Merge "Fix UDFPS icon of Biometric Prompt has no response after rotation" into tm-qpr-dev
diff --git a/core/java/android/animation/AnimationHandler.java b/core/java/android/animation/AnimationHandler.java
index 7e814af..1403ba2 100644
--- a/core/java/android/animation/AnimationHandler.java
+++ b/core/java/android/animation/AnimationHandler.java
@@ -432,8 +432,9 @@
 
     /**
      * Callbacks that receives notifications for animation timing and frame commit timing.
+     * @hide
      */
-    interface AnimationFrameCallback {
+    public interface AnimationFrameCallback {
         /**
          * Run animation based on the frame time.
          * @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index e837920..8b41aa4 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -8468,8 +8468,8 @@
             }
 
             int maxAvatarSize = resources.getDimensionPixelSize(
-                    isLowRam ? R.dimen.notification_person_icon_max_size
-                            : R.dimen.notification_person_icon_max_size_low_ram);
+                    isLowRam ? R.dimen.notification_person_icon_max_size_low_ram
+                            : R.dimen.notification_person_icon_max_size);
             if (mUser != null && mUser.getIcon() != null) {
                 mUser.getIcon().scaleDownIfNecessary(maxAvatarSize, maxAvatarSize);
             }
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index e022ca3..0ea53ce 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -2080,6 +2080,21 @@
     }
 
     /**
+     * Set the live wallpaper for the given screen(s).
+     *
+     * This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT
+     * permission. The caller must hold the INTERACT_ACROSS_USERS_FULL permission to change
+     * another user's wallpaper.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT)
+    public boolean setWallpaperComponentWithFlags(@NonNull ComponentName name,
+            @SetWallpaperFlags int which) {
+        return setWallpaperComponent(name);
+    }
+
+    /**
      * Set the display position of the current wallpaper within any larger space, when
      * that wallpaper is visible behind the given window.  The X and Y offsets
      * are floating point numbers ranging from 0 to 1, representing where the
diff --git a/core/java/android/app/servertransaction/PendingTransactionActions.java b/core/java/android/app/servertransaction/PendingTransactionActions.java
index a47fe82..8174778 100644
--- a/core/java/android/app/servertransaction/PendingTransactionActions.java
+++ b/core/java/android/app/servertransaction/PendingTransactionActions.java
@@ -25,11 +25,12 @@
 import android.os.PersistableBundle;
 import android.os.TransactionTooLargeException;
 import android.util.Log;
-import android.util.LogWriter;
 import android.util.Slog;
 
 import com.android.internal.util.IndentingPrintWriter;
 
+import java.io.StringWriter;
+
 /**
  * Container that has data pending to be used at later stages of
  * {@link android.app.servertransaction.ClientTransaction}.
@@ -134,6 +135,16 @@
             mDescription = description;
         }
 
+        private String collectBundleStates() {
+            final StringWriter writer = new StringWriter();
+            final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
+            pw.println("Bundle stats:");
+            Bundle.dumpStats(pw, mState);
+            pw.println("PersistableBundle stats:");
+            Bundle.dumpStats(pw, mPersistentState);
+            return writer.toString().stripTrailing();
+        }
+
         @Override
         public void run() {
             // Tell activity manager we have been stopped.
@@ -142,19 +153,24 @@
                 // TODO(lifecycler): Use interface callback instead of AMS.
                 ActivityClient.getInstance().activityStopped(
                         mActivity.token, mState, mPersistentState, mDescription);
-            } catch (RuntimeException ex) {
-                // Dump statistics about bundle to help developers debug
-                final LogWriter writer = new LogWriter(Log.WARN, TAG);
-                final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
-                pw.println("Bundle stats:");
-                Bundle.dumpStats(pw, mState);
-                pw.println("PersistableBundle stats:");
-                Bundle.dumpStats(pw, mPersistentState);
+            } catch (RuntimeException runtimeException) {
+                // Collect the statistics about bundle
+                final String bundleStats = collectBundleStates();
 
-                if (ex.getCause() instanceof TransactionTooLargeException
-                        && mActivity.packageInfo.getTargetSdkVersion() < Build.VERSION_CODES.N) {
-                    Log.e(TAG, "App sent too much data in instance state, so it was ignored", ex);
-                    return;
+                RuntimeException ex = runtimeException;
+                if (ex.getCause() instanceof TransactionTooLargeException) {
+                    // Embed the stats into exception message to help developers debug if the
+                    // transaction size is too large.
+                    final String message = ex.getMessage() + "\n" + bundleStats;
+                    ex = new RuntimeException(message, ex.getCause());
+                    if (mActivity.packageInfo.getTargetSdkVersion() < Build.VERSION_CODES.N) {
+                        Log.e(TAG, "App sent too much data in instance state, so it was ignored",
+                                ex);
+                        return;
+                    }
+                } else {
+                    // Otherwise, dump the stats anyway.
+                    Log.w(TAG, bundleStats);
                 }
                 throw ex;
             }
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index cc303fb..2dced96 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -329,6 +329,22 @@
     }
 
     /**
+     * Set the visibiity of all widgets associated with this host to hidden
+     *
+     * @hide
+     */
+    public void setAppWidgetHidden() {
+        if (sService == null) {
+            return;
+        }
+        try {
+            sService.setAppWidgetHidden(mContextOpPackageName, mHostId);
+        } catch (RemoteException e) {
+            throw new RuntimeException("System server dead?", e);
+        }
+    }
+
+    /**
      * Set the host's interaction handler.
      *
      * @hide
@@ -418,14 +434,7 @@
         AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);
         view.setInteractionHandler(mInteractionHandler);
         view.setAppWidget(appWidgetId, appWidget);
-        addListener(appWidgetId, view);
-        RemoteViews views;
-        try {
-            views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId);
-        } catch (RemoteException e) {
-            throw new RuntimeException("system server dead?", e);
-        }
-        view.updateAppWidget(views);
+        setListener(appWidgetId, view);
 
         return view;
     }
@@ -513,13 +522,19 @@
      * The AppWidgetHost retains a pointer to the newly-created listener.
      * @param appWidgetId The ID of the app widget for which to add the listener
      * @param listener The listener interface that deals with actions towards the widget view
-     *
      * @hide
      */
-    public void addListener(int appWidgetId, @NonNull AppWidgetHostListener listener) {
+    public void setListener(int appWidgetId, @NonNull AppWidgetHostListener listener) {
         synchronized (mListeners) {
             mListeners.put(appWidgetId, listener);
         }
+        RemoteViews views = null;
+        try {
+            views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId);
+        } catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+        listener.updateAppWidget(views);
     }
 
     /**
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 0f6010f..2a47851 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -272,6 +272,9 @@
                     @Override
                     public void onServiceConnected(ComponentName component, IBinder binder) {
                         mProxy = ICameraExtensionsProxyService.Stub.asInterface(binder);
+                        if (mProxy == null) {
+                            throw new IllegalStateException("Camera Proxy service is null");
+                        }
                         try {
                             mSupportsAdvancedExtensions = mProxy.advancedExtensionsSupported();
                         } catch (RemoteException e) {
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index 5ca0da2..8afd6de 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -335,4 +335,16 @@
 
     /** Allows power button to intercept a power key button press. */
     public abstract boolean interceptPowerKeyDown(KeyEvent event);
+
+    /**
+     * Internal version of {@link android.os.PowerManager#nap} which allows for napping while the
+     * device is not awake.
+     */
+    public abstract void nap(long eventTime, boolean allowWake);
+
+    /**
+     * Returns true if ambient display is suppressed by any app with any token. This method will
+     * return false if ambient display is not available.
+     */
+    public abstract boolean isAmbientDisplaySuppressed();
 }
diff --git a/core/java/android/service/dreams/DreamActivity.java b/core/java/android/service/dreams/DreamActivity.java
index f6a7c8e..a2fa139 100644
--- a/core/java/android/service/dreams/DreamActivity.java
+++ b/core/java/android/service/dreams/DreamActivity.java
@@ -44,6 +44,8 @@
 public class DreamActivity extends Activity {
     static final String EXTRA_CALLBACK = "binder";
     static final String EXTRA_DREAM_TITLE = "title";
+    @Nullable
+    private DreamService.DreamActivityCallbacks mCallback;
 
     public DreamActivity() {}
 
@@ -57,11 +59,19 @@
         }
 
         final Bundle extras = getIntent().getExtras();
-        final DreamService.DreamActivityCallback callback =
-                (DreamService.DreamActivityCallback) extras.getBinder(EXTRA_CALLBACK);
+        mCallback = (DreamService.DreamActivityCallbacks) extras.getBinder(EXTRA_CALLBACK);
 
-        if (callback != null) {
-            callback.onActivityCreated(this);
+        if (mCallback != null) {
+            mCallback.onActivityCreated(this);
         }
     }
+
+    @Override
+    public void onDestroy() {
+        if (mCallback != null) {
+            mCallback.onActivityDestroyed();
+        }
+
+        super.onDestroy();
+    }
 }
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 3c1fef0..32bdf79 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1047,7 +1047,7 @@
         }
 
         if (mDreamToken == null) {
-            Slog.w(mTag, "Finish was called before the dream was attached.");
+            if (mDebug) Slog.v(mTag, "finish() called when not attached.");
             stopSelf();
             return;
         }
@@ -1295,7 +1295,7 @@
             Intent i = new Intent(this, DreamActivity.class);
             i.setPackage(getApplicationContext().getPackageName());
             i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            i.putExtra(DreamActivity.EXTRA_CALLBACK, new DreamActivityCallback(mDreamToken));
+            i.putExtra(DreamActivity.EXTRA_CALLBACK, new DreamActivityCallbacks(mDreamToken));
             final ServiceInfo serviceInfo = fetchServiceInfo(this,
                     new ComponentName(this, getClass()));
             i.putExtra(DreamActivity.EXTRA_DREAM_TITLE, fetchDreamLabel(this, serviceInfo));
@@ -1488,10 +1488,10 @@
     }
 
     /** @hide */
-    final class DreamActivityCallback extends Binder {
+    final class DreamActivityCallbacks extends Binder {
         private final IBinder mActivityDreamToken;
 
-        DreamActivityCallback(IBinder token) {
+        DreamActivityCallbacks(IBinder token) {
             mActivityDreamToken = token;
         }
 
@@ -1516,6 +1516,12 @@
             mActivity = activity;
             onWindowCreated(activity.getWindow());
         }
+
+        // If DreamActivity is destroyed, wake up from Dream.
+        void onActivityDestroyed() {
+            mActivity = null;
+            onDestroy();
+        }
     }
 
     /**
diff --git a/core/java/android/service/wallpaper/IWallpaperService.aidl b/core/java/android/service/wallpaper/IWallpaperService.aidl
index 56e2486..f46c60f 100644
--- a/core/java/android/service/wallpaper/IWallpaperService.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperService.aidl
@@ -25,6 +25,6 @@
 oneway interface IWallpaperService {
     void attach(IWallpaperConnection connection,
             IBinder windowToken, int windowType, boolean isPreview,
-            int reqWidth, int reqHeight, in Rect padding, int displayId);
+            int reqWidth, int reqHeight, in Rect padding, int displayId, int which);
     void detach();
 }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 01749c0..e5792a9 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -18,6 +18,7 @@
 
 import static android.app.WallpaperManager.COMMAND_FREEZE;
 import static android.app.WallpaperManager.COMMAND_UNFREEZE;
+import static android.app.WallpaperManager.SetWallpaperFlags;
 import static android.graphics.Matrix.MSCALE_X;
 import static android.graphics.Matrix.MSCALE_Y;
 import static android.graphics.Matrix.MSKEW_X;
@@ -2427,7 +2428,7 @@
         @Override
         public void attach(IWallpaperConnection conn, IBinder windowToken,
                 int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
-                int displayId) {
+                int displayId, @SetWallpaperFlags int which) {
             mEngineWrapper = new IWallpaperEngineWrapper(mTarget, conn, windowToken,
                     windowType, isPreview, reqWidth, reqHeight, padding, displayId);
         }
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index 44f419a..e407707 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -241,6 +241,8 @@
      */
     public boolean willShowImeOnTarget;
 
+    public int rotationChange;
+
     public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
             Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
             Rect localBounds, Rect screenSpaceBounds,
@@ -302,6 +304,7 @@
         backgroundColor = in.readInt();
         showBackdrop = in.readBoolean();
         willShowImeOnTarget = in.readBoolean();
+        rotationChange = in.readInt();
     }
 
     public void setShowBackdrop(boolean shouldShowBackdrop) {
@@ -316,6 +319,14 @@
         return willShowImeOnTarget;
     }
 
+    public void setRotationChange(int rotationChange) {
+        this.rotationChange = rotationChange;
+    }
+
+    public int getRotationChange() {
+        return rotationChange;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -345,6 +356,7 @@
         dest.writeInt(backgroundColor);
         dest.writeBoolean(showBackdrop);
         dest.writeBoolean(willShowImeOnTarget);
+        dest.writeInt(rotationChange);
     }
 
     public void dump(PrintWriter pw, String prefix) {
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
new file mode 100644
index 0000000..dd4385c
--- /dev/null
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -0,0 +1,136 @@
+/*
+ * 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 android.window;
+
+import android.util.FloatProperty;
+
+import com.android.internal.dynamicanimation.animation.SpringAnimation;
+import com.android.internal.dynamicanimation.animation.SpringForce;
+
+/**
+ * An animator that drives the predictive back progress with a spring.
+ *
+ * The back gesture's latest touch point and committal state determines the final position of
+ * the spring. The continuous movement of the spring is used to produce {@link BackEvent}s with
+ * smoothly transitioning progress values.
+ *
+ * @hide
+ */
+public class BackProgressAnimator {
+    /**
+     *  A factor to scale the input progress by, so that it works better with the spring.
+     *  We divide the output progress by this value before sending it to apps, so that apps
+     *  always receive progress values in [0, 1].
+     */
+    private static final float SCALE_FACTOR = 100f;
+    private final SpringAnimation mSpring;
+    private ProgressCallback mCallback;
+    private float mProgress = 0;
+    private BackEvent mLastBackEvent;
+    private boolean mStarted = false;
+
+    private void setProgress(float progress) {
+        mProgress = progress;
+    }
+
+    private float getProgress() {
+        return mProgress;
+    }
+
+    private static final FloatProperty<BackProgressAnimator> PROGRESS_PROP =
+            new FloatProperty<BackProgressAnimator>("progress") {
+                @Override
+                public void setValue(BackProgressAnimator animator, float value) {
+                    animator.setProgress(value);
+                    animator.updateProgressValue(value);
+                }
+
+                @Override
+                public Float get(BackProgressAnimator object) {
+                    return object.getProgress();
+                }
+            };
+
+
+    /** A callback to be invoked when there's a progress value update from the animator. */
+    public interface ProgressCallback {
+        /** Called when there's a progress value update. */
+        void onProgressUpdate(BackEvent event);
+    }
+
+    public BackProgressAnimator() {
+        mSpring = new SpringAnimation(this, PROGRESS_PROP);
+        mSpring.setSpring(new SpringForce()
+                .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+                .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
+    }
+
+    /**
+     * Sets a new target position for the back progress.
+     *
+     * @param event the {@link BackEvent} containing the latest target progress.
+     */
+    public void onBackProgressed(BackEvent event) {
+        if (!mStarted) {
+            return;
+        }
+        mLastBackEvent = event;
+        mSpring.animateToFinalPosition(event.getProgress() * SCALE_FACTOR);
+    }
+
+    /**
+     * Starts the back progress animation.
+     *
+     * @param event the {@link BackEvent} that started the gesture.
+     * @param callback the back callback to invoke for the gesture. It will receive back progress
+     *                 dispatches as the progress animation updates.
+     */
+    public void onBackStarted(BackEvent event, ProgressCallback callback) {
+        reset();
+        mLastBackEvent = event;
+        mCallback = callback;
+        mStarted = true;
+    }
+
+    /**
+     * Resets the back progress animation. This should be called when back is invoked or cancelled.
+     */
+    public void reset() {
+        mSpring.animateToFinalPosition(0);
+        if (mSpring.canSkipToEnd()) {
+            mSpring.skipToEnd();
+        } else {
+            // Should never happen.
+            mSpring.cancel();
+        }
+        mStarted = false;
+        mLastBackEvent = null;
+        mCallback = null;
+        mProgress = 0;
+    }
+
+    private void updateProgressValue(float progress) {
+        if (mLastBackEvent == null || mCallback == null || !mStarted) {
+            return;
+        }
+        mCallback.onProgressUpdate(
+                new BackEvent(mLastBackEvent.getTouchX(), mLastBackEvent.getTouchY(),
+                        progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge(),
+                        mLastBackEvent.getDepartingAnimationTarget()));
+    }
+
+}
diff --git a/core/java/android/window/IOnBackInvokedCallback.aidl b/core/java/android/window/IOnBackInvokedCallback.aidl
index 47796de..6af8ddd 100644
--- a/core/java/android/window/IOnBackInvokedCallback.aidl
+++ b/core/java/android/window/IOnBackInvokedCallback.aidl
@@ -28,17 +28,18 @@
 oneway interface IOnBackInvokedCallback {
    /**
     * Called when a back gesture has been started, or back button has been pressed down.
-    * Wraps {@link OnBackInvokedCallback#onBackStarted()}.
+    * Wraps {@link OnBackInvokedCallback#onBackStarted(BackEvent)}.
+    *
+    * @param backEvent The {@link BackEvent} containing information about the touch or button press.
     */
-    void onBackStarted();
+    void onBackStarted(in BackEvent backEvent);
 
     /**
      * Called on back gesture progress.
-     * Wraps {@link OnBackInvokedCallback#onBackProgressed()}.
+     * Wraps {@link OnBackInvokedCallback#onBackProgressed(BackEvent)}.
      *
-     * @param touchX Absolute X location of the touch point.
-     * @param touchY Absolute Y location of the touch point.
-     * @param progress Value between 0 and 1 on how far along the back gesture is.
+     * @param backEvent The {@link BackEvent} containing information about the latest touch point
+     *                  and the progress that the back animation should seek to.
      */
     void onBackProgressed(in BackEvent backEvent);
 
diff --git a/core/java/android/window/OnBackAnimationCallback.java b/core/java/android/window/OnBackAnimationCallback.java
index 1a37e57..5823084 100644
--- a/core/java/android/window/OnBackAnimationCallback.java
+++ b/core/java/android/window/OnBackAnimationCallback.java
@@ -40,10 +40,12 @@
  * @hide
  */
 public interface OnBackAnimationCallback extends OnBackInvokedCallback {
-   /**
-    * Called when a back gesture has been started, or back button has been pressed down.
-    */
-    default void onBackStarted() { }
+    /**
+     * Called when a back gesture has been started, or back button has been pressed down.
+     *
+     * @param backEvent An {@link BackEvent} object describing the progress event.
+     */
+    default void onBackStarted(@NonNull BackEvent backEvent) {}
 
     /**
      * Called on back gesture progress.
diff --git a/core/java/android/window/OnBackInvokedCallback.java b/core/java/android/window/OnBackInvokedCallback.java
index 6e2d4f9..62c41bf 100644
--- a/core/java/android/window/OnBackInvokedCallback.java
+++ b/core/java/android/window/OnBackInvokedCallback.java
@@ -16,6 +16,7 @@
 
 package android.window;
 
+import android.annotation.NonNull;
 import android.app.Activity;
 import android.app.Dialog;
 import android.view.Window;
@@ -41,8 +42,35 @@
 @SuppressWarnings("deprecation")
 public interface OnBackInvokedCallback {
     /**
+     * Called when a back gesture has been started, or back button has been pressed down.
+     *
+     * @param backEvent The {@link BackEvent} containing information about the touch or
+     *                  button press.
+     *
+     * @hide
+     */
+    default void onBackStarted(@NonNull BackEvent backEvent) {}
+
+    /**
+     * Called when a back gesture has been progressed.
+     *
+     * @param backEvent The {@link BackEvent} containing information about the latest touch point
+     *                  and the progress that the back animation should seek to.
+     *
+     * @hide
+     */
+    default void onBackProgressed(@NonNull BackEvent backEvent) {}
+
+    /**
      * Called when a back gesture has been completed and committed, or back button pressed
      * has been released and committed.
      */
     void onBackInvoked();
+
+    /**
+     * Called when a back gesture or button press has been cancelled.
+     *
+     * @hide
+     */
+    default void onBackCancelled() {}
 }
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 0730f3d..fda39c1 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -218,19 +218,24 @@
     public Checker getChecker() {
         return mChecker;
     }
+    @NonNull
+    private static final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
 
     static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
         private final WeakReference<OnBackInvokedCallback> mCallback;
+
         OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) {
             mCallback = new WeakReference<>(callback);
         }
 
         @Override
-        public void onBackStarted() {
+        public void onBackStarted(BackEvent backEvent) {
             Handler.getMain().post(() -> {
                 final OnBackAnimationCallback callback = getBackAnimationCallback();
                 if (callback != null) {
-                    callback.onBackStarted();
+                    mProgressAnimator.onBackStarted(backEvent, event ->
+                            callback.onBackProgressed(event));
+                    callback.onBackStarted(backEvent);
                 }
             });
         }
@@ -240,7 +245,7 @@
             Handler.getMain().post(() -> {
                 final OnBackAnimationCallback callback = getBackAnimationCallback();
                 if (callback != null) {
-                    callback.onBackProgressed(backEvent);
+                    mProgressAnimator.onBackProgressed(backEvent);
                 }
             });
         }
@@ -248,6 +253,7 @@
         @Override
         public void onBackCancelled() {
             Handler.getMain().post(() -> {
+                mProgressAnimator.reset();
                 final OnBackAnimationCallback callback = getBackAnimationCallback();
                 if (callback != null) {
                     callback.onBackCancelled();
@@ -258,6 +264,7 @@
         @Override
         public void onBackInvoked() throws RemoteException {
             Handler.getMain().post(() -> {
+                mProgressAnimator.reset();
                 final OnBackInvokedCallback callback = mCallback.get();
                 if (callback == null) {
                     return;
diff --git a/core/java/com/android/internal/app/AppLocaleCollector.java b/core/java/com/android/internal/app/AppLocaleCollector.java
new file mode 100644
index 0000000..65e8c64
--- /dev/null
+++ b/core/java/com/android/internal/app/AppLocaleCollector.java
@@ -0,0 +1,139 @@
+/*
+ * 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.internal.app;
+
+import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET;
+import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.LocaleList;
+import android.util.Log;
+
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+/** The Locale data collector for per-app language. */
+class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase {
+    private static final String TAG = AppLocaleCollector.class.getSimpleName();
+    private final Context mContext;
+    private final String mAppPackageName;
+    private final LocaleStore.LocaleInfo mAppCurrentLocale;
+
+    AppLocaleCollector(Context context, String appPackageName) {
+        mContext = context;
+        mAppPackageName = appPackageName;
+        mAppCurrentLocale = LocaleStore.getAppCurrentLocaleInfo(
+                mContext, mAppPackageName);
+    }
+
+    @Override
+    public HashSet<String> getIgnoredLocaleList(boolean translatedOnly) {
+        HashSet<String> langTagsToIgnore = new HashSet<>();
+
+        LocaleList systemLangList = LocaleList.getDefault();
+        for(int i = 0; i < systemLangList.size(); i++) {
+            langTagsToIgnore.add(systemLangList.get(i).toLanguageTag());
+        }
+
+        if (mAppCurrentLocale != null) {
+            langTagsToIgnore.add(mAppCurrentLocale.getLocale().toLanguageTag());
+        }
+        return langTagsToIgnore;
+    }
+
+    @Override
+    public Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent,
+            boolean translatedOnly, boolean isForCountryMode) {
+        AppLocaleStore.AppLocaleResult result =
+                AppLocaleStore.getAppSupportedLocales(mContext, mAppPackageName);
+        Set<String> langTagsToIgnore = getIgnoredLocaleList(translatedOnly);
+        Set<LocaleStore.LocaleInfo> appLocaleList = new HashSet<>();
+        Set<LocaleStore.LocaleInfo> systemLocaleList;
+        boolean shouldShowList =
+                result.mLocaleStatus == GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG
+                        || result.mLocaleStatus == GET_SUPPORTED_LANGUAGE_FROM_ASSET;
+
+        // Get system supported locale list
+        if (isForCountryMode) {
+            systemLocaleList = LocaleStore.getLevelLocales(mContext,
+                    langTagsToIgnore, parent, translatedOnly);
+        } else {
+            systemLocaleList = LocaleStore.getLevelLocales(mContext, langTagsToIgnore,
+                    null /* no parent */, translatedOnly);
+        }
+
+        // Add current app locale
+        if (mAppCurrentLocale != null && !isForCountryMode) {
+            appLocaleList.add(mAppCurrentLocale);
+        }
+
+        // Add current system language into suggestion list
+        for(LocaleStore.LocaleInfo localeInfo:
+                LocaleStore.getSystemCurrentLocaleInfo()) {
+            boolean isNotCurrentLocale = mAppCurrentLocale == null
+                    || !localeInfo.getLocale().equals(mAppCurrentLocale.getLocale());
+            if (!isForCountryMode && isNotCurrentLocale) {
+                appLocaleList.add(localeInfo);
+            }
+        }
+
+        // Add the languages that included in system supported locale
+        if (shouldShowList) {
+            appLocaleList.addAll(filterTheLanguagesNotIncludedInSystemLocale(
+                    systemLocaleList, result.mAppSupportedLocales));
+        }
+
+        // Add "system language" option
+        if (!isForCountryMode && shouldShowList) {
+            appLocaleList.add(LocaleStore.getSystemDefaultLocaleInfo(
+                    mAppCurrentLocale == null));
+        }
+
+        if (Build.isDebuggable()) {
+            Log.d(TAG, "App locale list: " + appLocaleList);
+        }
+
+        return appLocaleList;
+    }
+
+    @Override
+    public boolean hasSpecificPackageName() {
+        return true;
+    }
+
+    private Set<LocaleStore.LocaleInfo> filterTheLanguagesNotIncludedInSystemLocale(
+            Set<LocaleStore.LocaleInfo> systemLocaleList,
+            HashSet<Locale> appSupportedLocales) {
+        Set<LocaleStore.LocaleInfo> filteredList = new HashSet<>();
+
+        for(LocaleStore.LocaleInfo li: systemLocaleList) {
+            if (appSupportedLocales.contains(li.getLocale())) {
+                filteredList.add(li);
+            } else {
+                for(Locale l: appSupportedLocales) {
+                    if(LocaleList.matchesLanguageAndScript(li.getLocale(), l)) {
+                        filteredList.add(li);
+                        break;
+                    }
+                }
+            }
+        }
+        return filteredList;
+    }
+}
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index 965895f..3efd279 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -16,16 +16,12 @@
 
 package com.android.internal.app;
 
-import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus;
-
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
 import android.app.ListFragment;
 import android.content.Context;
 import android.os.Bundle;
-import android.os.LocaleList;
 import android.text.TextUtils;
-import android.util.Log;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -36,7 +32,6 @@
 
 import com.android.internal.R;
 
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.Locale;
 import java.util.Set;
@@ -54,6 +49,7 @@
 
     private SuggestedLocaleAdapter mAdapter;
     private LocaleSelectedListener mListener;
+    private LocaleCollectorBase mLocalePickerCollector;
     private Set<LocaleStore.LocaleInfo> mLocaleList;
     private LocaleStore.LocaleInfo mParentLocale;
     private boolean mTranslatedOnly = false;
@@ -62,7 +58,6 @@
     private boolean mPreviousSearchHadFocus = false;
     private int mFirstVisiblePosition = 0;
     private int mTopDistance = 0;
-    private String mAppPackageName;
     private CharSequence mTitle = null;
     private OnActionExpandListener mOnActionExpandListener;
 
@@ -79,31 +74,50 @@
         void onLocaleSelected(LocaleStore.LocaleInfo locale);
     }
 
-    private static LocalePickerWithRegion createCountryPicker(Context context,
+    /**
+     * The interface which provides the locale list.
+     */
+    interface LocaleCollectorBase {
+        /** Gets the ignored locale list. */
+        HashSet<String> getIgnoredLocaleList(boolean translatedOnly);
+
+        /** Gets the supported locale list. */
+        Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent,
+                boolean translatedOnly, boolean isForCountryMode);
+
+        /** Indicates if the class work for specific package. */
+        boolean hasSpecificPackageName();
+    }
+
+    private static LocalePickerWithRegion createCountryPicker(
             LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
-            boolean translatedOnly, String appPackageName,
-            OnActionExpandListener onActionExpandListener) {
+            boolean translatedOnly, OnActionExpandListener onActionExpandListener,
+            LocaleCollectorBase localePickerCollector) {
         LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
         localePicker.setOnActionExpandListener(onActionExpandListener);
-        boolean shouldShowTheList = localePicker.setListener(context, listener, parent,
-                translatedOnly, appPackageName);
+        boolean shouldShowTheList = localePicker.setListener(listener, parent,
+                translatedOnly, localePickerCollector);
         return shouldShowTheList ? localePicker : null;
     }
 
     public static LocalePickerWithRegion createLanguagePicker(Context context,
             LocaleSelectedListener listener, boolean translatedOnly) {
-        LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
-        localePicker.setListener(context, listener, /* parent */ null, translatedOnly, null);
-        return localePicker;
+        return createLanguagePicker(context, listener, translatedOnly, null, null);
     }
 
     public static LocalePickerWithRegion createLanguagePicker(Context context,
             LocaleSelectedListener listener, boolean translatedOnly, String appPackageName,
             OnActionExpandListener onActionExpandListener) {
+        LocaleCollectorBase localePickerController;
+        if (TextUtils.isEmpty(appPackageName)) {
+            localePickerController = new SystemLocaleCollector(context);
+        } else {
+            localePickerController = new AppLocaleCollector(context, appPackageName);
+        }
         LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
         localePicker.setOnActionExpandListener(onActionExpandListener);
-        localePicker.setListener(
-                context, listener, /* parent */ null, translatedOnly, appPackageName);
+        localePicker.setListener(listener, /* parent */ null, translatedOnly,
+                localePickerController);
         return localePicker;
     }
 
@@ -120,109 +134,23 @@
      * In this case we don't even show the list, we call the listener with that locale,
      * "pretending" it was selected, and return false.</p>
      */
-    private boolean setListener(Context context, LocaleSelectedListener listener,
-            LocaleStore.LocaleInfo parent, boolean translatedOnly, String appPackageName) {
+    private boolean setListener(LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
+            boolean translatedOnly, LocaleCollectorBase localePickerController) {
         this.mParentLocale = parent;
         this.mListener = listener;
         this.mTranslatedOnly = translatedOnly;
-        this.mAppPackageName = appPackageName;
+        this.mLocalePickerCollector = localePickerController;
         setRetainInstance(true);
 
-        final HashSet<String> langTagsToIgnore = new HashSet<>();
-        LocaleStore.LocaleInfo appCurrentLocale =
-                LocaleStore.getAppCurrentLocaleInfo(context, appPackageName);
-        boolean isForCountryMode = parent != null;
+        mLocaleList = localePickerController.getSupportedLocaleList(
+                parent, translatedOnly, parent != null);
 
-        if (!TextUtils.isEmpty(appPackageName) && !isForCountryMode) {
-            // Filter current system locale to add them into suggestion
-            LocaleList systemLangList = LocaleList.getDefault();
-            for(int i = 0; i < systemLangList.size(); i++) {
-                langTagsToIgnore.add(systemLangList.get(i).toLanguageTag());
-            }
-
-            if (appCurrentLocale != null) {
-                Log.d(TAG, "appCurrentLocale: " + appCurrentLocale.getLocale().toLanguageTag());
-                langTagsToIgnore.add(appCurrentLocale.getLocale().toLanguageTag());
-            } else {
-                Log.d(TAG, "appCurrentLocale is null");
-            }
-        } else if (!translatedOnly) {
-            final LocaleList userLocales = LocalePicker.getLocales();
-            final String[] langTags = userLocales.toLanguageTags().split(",");
-            Collections.addAll(langTagsToIgnore, langTags);
-        }
-
-        if (isForCountryMode) {
-            mLocaleList = LocaleStore.getLevelLocales(context,
-                    langTagsToIgnore, parent, translatedOnly);
-            if (mLocaleList.size() <= 1) {
-                if (listener != null && (mLocaleList.size() == 1)) {
-                    listener.onLocaleSelected(mLocaleList.iterator().next());
-                }
-                return false;
-            }
+        if (parent != null && listener != null && mLocaleList.size() == 1) {
+            listener.onLocaleSelected(mLocaleList.iterator().next());
+            return false;
         } else {
-            mLocaleList = LocaleStore.getLevelLocales(context, langTagsToIgnore,
-                    null /* no parent */, translatedOnly);
+            return true;
         }
-        Log.d(TAG, "mLocaleList size:  " + mLocaleList.size());
-
-        // Adding current locale and system default option into suggestion list
-        if(!TextUtils.isEmpty(appPackageName)) {
-            if (appCurrentLocale != null && !isForCountryMode) {
-                mLocaleList.add(appCurrentLocale);
-            }
-
-            AppLocaleStore.AppLocaleResult result =
-                    AppLocaleStore.getAppSupportedLocales(context, appPackageName);
-            boolean shouldShowList =
-                    result.mLocaleStatus == LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG
-                    || result.mLocaleStatus == LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET;
-
-            // Add current system language into suggestion list
-            for(LocaleStore.LocaleInfo localeInfo: LocaleStore.getSystemCurrentLocaleInfo()) {
-                boolean isNotCurrentLocale = appCurrentLocale == null
-                        || !localeInfo.getLocale().equals(appCurrentLocale.getLocale());
-                if (!isForCountryMode && isNotCurrentLocale) {
-                    mLocaleList.add(localeInfo);
-                }
-            }
-
-            // Filter the language not support in app
-            mLocaleList = filterTheLanguagesNotSupportedInApp(
-                    shouldShowList, result.mAppSupportedLocales);
-
-            Log.d(TAG, "mLocaleList after app-supported filter:  " + mLocaleList.size());
-
-            // Add "system language"
-            if (!isForCountryMode && shouldShowList) {
-                mLocaleList.add(LocaleStore.getSystemDefaultLocaleInfo(appCurrentLocale == null));
-            }
-        }
-        return true;
-    }
-
-    private Set<LocaleStore.LocaleInfo> filterTheLanguagesNotSupportedInApp(
-            boolean shouldShowList, HashSet<Locale> supportedLocales) {
-        Set<LocaleStore.LocaleInfo> filteredList = new HashSet<>();
-        if (!shouldShowList) {
-            return filteredList;
-        }
-
-        for(LocaleStore.LocaleInfo li: mLocaleList) {
-            if (supportedLocales.contains(li.getLocale())) {
-                filteredList.add(li);
-            } else {
-                for(Locale l: supportedLocales) {
-                    if(LocaleList.matchesLanguageAndScript(li.getLocale(), l)) {
-                        filteredList.add(li);
-                        break;
-                    }
-                }
-            }
-        }
-
-        return filteredList;
     }
 
     private void returnToParentFrame() {
@@ -246,7 +174,9 @@
         mTitle = getActivity().getTitle();
         final boolean countryMode = mParentLocale != null;
         final Locale sortingLocale = countryMode ? mParentLocale.getLocale() : Locale.getDefault();
-        mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode, mAppPackageName);
+        final boolean hasSpecificPackageName =
+                mLocalePickerCollector != null && mLocalePickerCollector.hasSpecificPackageName();
+        mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode, hasSpecificPackageName);
         final LocaleHelper.LocaleInfoComparator comp =
                 new LocaleHelper.LocaleInfoComparator(sortingLocale, countryMode);
         mAdapter.sort(comp);
@@ -321,8 +251,8 @@
             returnToParentFrame();
         } else {
             LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker(
-                    getContext(), mListener, locale, mTranslatedOnly /* translate only */,
-                    mAppPackageName, mOnActionExpandListener);
+                    mListener, locale, mTranslatedOnly /* translate only */,
+                    mOnActionExpandListener, this.mLocalePickerCollector);
             if (selector != null) {
                 getFragmentManager().beginTransaction()
                         .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
@@ -340,7 +270,8 @@
             inflater.inflate(R.menu.language_selection_list, menu);
 
             final MenuItem searchMenuItem = menu.findItem(R.id.locale_search_menu);
-            if (!TextUtils.isEmpty(mAppPackageName) && mOnActionExpandListener != null) {
+            if (mLocalePickerCollector.hasSpecificPackageName()
+                    && mOnActionExpandListener != null) {
                 searchMenuItem.setOnActionExpandListener(mOnActionExpandListener);
             }
 
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 4a1f7eb..42b46cd 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -647,15 +647,16 @@
 
         if (info instanceof DisplayResolveInfo) {
             DisplayResolveInfo dri = (DisplayResolveInfo) info;
-            boolean hasLabel = dri.hasDisplayLabel();
-            holder.bindLabel(
-                    dri.getDisplayLabel(),
-                    dri.getExtendedInfo(),
-                    hasLabel && alwaysShowSubLabel());
-            holder.bindIcon(info);
-            if (!hasLabel) {
+            if (dri.hasDisplayLabel()) {
+                holder.bindLabel(
+                        dri.getDisplayLabel(),
+                        dri.getExtendedInfo(),
+                        alwaysShowSubLabel());
+            } else {
+                holder.bindLabel("", "", false);
                 loadLabel(dri);
             }
+            holder.bindIcon(info);
             if (!dri.hasDisplayIcon()) {
                 loadIcon(dri);
             }
diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
index 1be1247..5ed0e8b 100644
--- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
+++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
@@ -70,17 +70,17 @@
     private Locale mDisplayLocale = null;
     // used to potentially cache a modified Context that uses mDisplayLocale
     private Context mContextOverride = null;
-    private String mAppPackageName;
+    private boolean mHasSpecificAppPackageName;
 
     public SuggestedLocaleAdapter(Set<LocaleStore.LocaleInfo> localeOptions, boolean countryMode) {
-        this(localeOptions, countryMode, null);
+        this(localeOptions, countryMode, false);
     }
 
     public SuggestedLocaleAdapter(Set<LocaleStore.LocaleInfo> localeOptions, boolean countryMode,
-            String appPackageName) {
+            boolean hasSpecificAppPackageName) {
         mCountryMode = countryMode;
         mLocaleOptions = new ArrayList<>(localeOptions.size());
-        mAppPackageName = appPackageName;
+        mHasSpecificAppPackageName = hasSpecificAppPackageName;
 
         for (LocaleStore.LocaleInfo li : localeOptions) {
             if (li.isSuggested()) {
@@ -134,7 +134,7 @@
 
     @Override
     public int getViewTypeCount() {
-        if (!TextUtils.isEmpty(mAppPackageName) && showHeaders()) {
+        if (mHasSpecificAppPackageName && showHeaders()) {
             // Two headers, 1 "System language", 1 current locale
             return APP_LANGUAGE_PICKER_TYPE_COUNT;
         } else if (showHeaders()) {
diff --git a/core/java/com/android/internal/app/SystemLocaleCollector.java b/core/java/com/android/internal/app/SystemLocaleCollector.java
new file mode 100644
index 0000000..9a6d4c1
--- /dev/null
+++ b/core/java/com/android/internal/app/SystemLocaleCollector.java
@@ -0,0 +1,66 @@
+/*
+ * 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.internal.app;
+
+import android.content.Context;
+import android.os.LocaleList;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/** The Locale data collector for System language. */
+class SystemLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase {
+    private final Context mContext;
+
+    SystemLocaleCollector(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public HashSet<String> getIgnoredLocaleList(boolean translatedOnly) {
+        HashSet<String> ignoreList = new HashSet<>();
+        if (!translatedOnly) {
+            final LocaleList userLocales = LocalePicker.getLocales();
+            final String[] langTags = userLocales.toLanguageTags().split(",");
+            Collections.addAll(ignoreList, langTags);
+        }
+        return ignoreList;
+    }
+
+    @Override
+    public Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent,
+            boolean translatedOnly, boolean isForCountryMode) {
+        Set<String> langTagsToIgnore = getIgnoredLocaleList(translatedOnly);
+        Set<LocaleStore.LocaleInfo> localeList;
+
+        if (isForCountryMode) {
+            localeList = LocaleStore.getLevelLocales(mContext,
+                    langTagsToIgnore, parent, translatedOnly);
+        } else {
+            localeList = LocaleStore.getLevelLocales(mContext, langTagsToIgnore,
+                    null /* no parent */, translatedOnly);
+        }
+        return localeList;
+    }
+
+
+    @Override
+    public boolean hasSpecificPackageName() {
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index 2d68cb4..51b56db 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -45,6 +45,7 @@
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     RemoteViews getAppWidgetViews(String callingPackage, int appWidgetId);
     int[] getAppWidgetIdsForHost(String callingPackage, int hostId);
+    void setAppWidgetHidden(in String callingPackage, int hostId);
     IntentSender createAppWidgetConfigIntentSender(String callingPackage, int appWidgetId,
             int intentFlags);
 
diff --git a/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java b/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java
new file mode 100644
index 0000000..d4fe7c8d
--- /dev/null
+++ b/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java
@@ -0,0 +1,815 @@
+/*
+ * 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.internal.dynamicanimation.animation;
+
+import android.animation.AnimationHandler;
+import android.animation.ValueAnimator;
+import android.annotation.FloatRange;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.os.Looper;
+import android.util.AndroidRuntimeException;
+import android.util.FloatProperty;
+import android.view.View;
+
+import java.util.ArrayList;
+
+/**
+ * This class is the base class of physics-based animations. It manages the animation's
+ * lifecycle such as {@link #start()} and {@link #cancel()}. This base class also handles the common
+ * setup for all the subclass animations. For example, DynamicAnimation supports adding
+ * {@link OnAnimationEndListener} and {@link OnAnimationUpdateListener} so that the important
+ * animation events can be observed through the callbacks. The start conditions for any subclass of
+ * DynamicAnimation can be set using {@link #setStartValue(float)} and
+ * {@link #setStartVelocity(float)}.
+ *
+ * @param <T> subclass of DynamicAnimation
+ */
+public abstract class DynamicAnimation<T extends DynamicAnimation<T>>
+        implements AnimationHandler.AnimationFrameCallback {
+
+    /**
+     * ViewProperty holds the access of a property of a {@link View}. When an animation is
+     * created with a {@link ViewProperty} instance, the corresponding property value of the view
+     * will be updated through this ViewProperty instance.
+     */
+    public abstract static class ViewProperty extends FloatProperty<View> {
+        private ViewProperty(String name) {
+            super(name);
+        }
+    }
+
+    /**
+     * View's translationX property.
+     */
+    public static final ViewProperty TRANSLATION_X = new ViewProperty("translationX") {
+        @Override
+        public void setValue(View view, float value) {
+            view.setTranslationX(value);
+        }
+
+        @Override
+        public Float get(View view) {
+            return view.getTranslationX();
+        }
+    };
+
+    /**
+     * View's translationY property.
+     */
+    public static final ViewProperty TRANSLATION_Y = new ViewProperty("translationY") {
+        @Override
+        public void setValue(View view, float value) {
+            view.setTranslationY(value);
+        }
+
+        @Override
+        public Float get(View view) {
+            return view.getTranslationY();
+        }
+    };
+
+    /**
+     * View's translationZ property.
+     */
+    public static final ViewProperty TRANSLATION_Z = new ViewProperty("translationZ") {
+        @Override
+        public void setValue(View view, float value) {
+            view.setTranslationZ(value);
+        }
+
+        @Override
+        public Float get(View view) {
+            return view.getTranslationZ();
+        }
+    };
+
+    /**
+     * View's scaleX property.
+     */
+    public static final ViewProperty SCALE_X = new ViewProperty("scaleX") {
+        @Override
+        public void setValue(View view, float value) {
+            view.setScaleX(value);
+        }
+
+        @Override
+        public Float get(View view) {
+            return view.getScaleX();
+        }
+    };
+
+    /**
+     * View's scaleY property.
+     */
+    public static final ViewProperty SCALE_Y = new ViewProperty("scaleY") {
+        @Override
+        public void setValue(View view, float value) {
+            view.setScaleY(value);
+        }
+
+        @Override
+        public Float get(View view) {
+            return view.getScaleY();
+        }
+    };
+
+    /**
+     * View's rotation property.
+     */
+    public static final ViewProperty ROTATION = new ViewProperty("rotation") {
+        @Override
+        public void setValue(View view, float value) {
+            view.setRotation(value);
+        }
+
+        @Override
+        public Float get(View view) {
+            return view.getRotation();
+        }
+    };
+
+    /**
+     * View's rotationX property.
+     */
+    public static final ViewProperty ROTATION_X = new ViewProperty("rotationX") {
+        @Override
+        public void setValue(View view, float value) {
+            view.setRotationX(value);
+        }
+
+        @Override
+        public Float get(View view) {
+            return view.getRotationX();
+        }
+    };
+
+    /**
+     * View's rotationY property.
+     */
+    public static final ViewProperty ROTATION_Y = new ViewProperty("rotationY") {
+        @Override
+        public void setValue(View view, float value) {
+            view.setRotationY(value);
+        }
+
+        @Override
+        public Float get(View view) {
+            return view.getRotationY();
+        }
+    };
+
+    /**
+     * View's x property.
+     */
+    public static final ViewProperty X = new ViewProperty("x") {
+        @Override
+        public void setValue(View view, float value) {
+            view.setX(value);
+        }
+
+        @Override
+        public Float get(View view) {
+            return view.getX();
+        }
+    };
+
+    /**
+     * View's y property.
+     */
+    public static final ViewProperty Y = new ViewProperty("y") {
+        @Override
+        public void setValue(View view, float value) {
+            view.setY(value);
+        }
+
+        @Override
+        public Float get(View view) {
+            return view.getY();
+        }
+    };
+
+    /**
+     * View's z property.
+     */
+    public static final ViewProperty Z = new ViewProperty("z") {
+        @Override
+        public void setValue(View view, float value) {
+            view.setZ(value);
+        }
+
+        @Override
+        public Float get(View view) {
+            return view.getZ();
+        }
+    };
+
+    /**
+     * View's alpha property.
+     */
+    public static final ViewProperty ALPHA = new ViewProperty("alpha") {
+        @Override
+        public void setValue(View view, float value) {
+            view.setAlpha(value);
+        }
+
+        @Override
+        public Float get(View view) {
+            return view.getAlpha();
+        }
+    };
+
+    // Properties below are not RenderThread compatible
+    /**
+     * View's scrollX property.
+     */
+    public static final ViewProperty SCROLL_X = new ViewProperty("scrollX") {
+        @Override
+        public void setValue(View view, float value) {
+            view.setScrollX((int) value);
+        }
+
+        @Override
+        public Float get(View view) {
+            return (float) view.getScrollX();
+        }
+    };
+
+    /**
+     * View's scrollY property.
+     */
+    public static final ViewProperty SCROLL_Y = new ViewProperty("scrollY") {
+        @Override
+        public void setValue(View view, float value) {
+            view.setScrollY((int) value);
+        }
+
+        @Override
+        public Float get(View view) {
+            return (float) view.getScrollY();
+        }
+    };
+
+    /**
+     * The minimum visible change in pixels that can be visible to users.
+     */
+    @SuppressLint("MinMaxConstant")
+    public static final float MIN_VISIBLE_CHANGE_PIXELS = 1f;
+    /**
+     * The minimum visible change in degrees that can be visible to users.
+     */
+    @SuppressLint("MinMaxConstant")
+    public static final float MIN_VISIBLE_CHANGE_ROTATION_DEGREES = 1f / 10f;
+    /**
+     * The minimum visible change in alpha that can be visible to users.
+     */
+    @SuppressLint("MinMaxConstant")
+    public static final float MIN_VISIBLE_CHANGE_ALPHA = 1f / 256f;
+    /**
+     * The minimum visible change in scale that can be visible to users.
+     */
+    @SuppressLint("MinMaxConstant")
+    public static final float MIN_VISIBLE_CHANGE_SCALE = 1f / 500f;
+
+    // Use the max value of float to indicate an unset state.
+    private static final float UNSET = Float.MAX_VALUE;
+
+    // Multiplier to the min visible change value for value threshold
+    private static final float THRESHOLD_MULTIPLIER = 0.75f;
+
+    // Internal tracking for velocity.
+    float mVelocity = 0;
+
+    // Internal tracking for value.
+    float mValue = UNSET;
+
+    // Tracks whether start value is set. If not, the animation will obtain the value at the time
+    // of starting through the getter and use that as the starting value of the animation.
+    boolean mStartValueIsSet = false;
+
+    // Target to be animated.
+    final Object mTarget;
+
+    // View property id.
+    final FloatProperty mProperty;
+
+    // Package private tracking of animation lifecycle state. Visible to subclass animations.
+    boolean mRunning = false;
+
+    // Min and max values that defines the range of the animation values.
+    float mMaxValue = Float.MAX_VALUE;
+    float mMinValue = -mMaxValue;
+
+    // Last frame time. Always gets reset to -1  at the end of the animation.
+    private long mLastFrameTime = 0;
+
+    private float mMinVisibleChange;
+
+    // List of end listeners
+    private final ArrayList<OnAnimationEndListener> mEndListeners = new ArrayList<>();
+
+    // List of update listeners
+    private final ArrayList<OnAnimationUpdateListener> mUpdateListeners = new ArrayList<>();
+
+    // Animation handler used to schedule updates for this animation.
+    private AnimationHandler mAnimationHandler;
+
+    // Internal state for value/velocity pair.
+    static class MassState {
+        float mValue;
+        float mVelocity;
+    }
+
+    /**
+     * Creates a dynamic animation with the given FloatValueHolder instance.
+     *
+     * @param floatValueHolder the FloatValueHolder instance to be animated.
+     */
+    DynamicAnimation(final FloatValueHolder floatValueHolder) {
+        mTarget = null;
+        mProperty = new FloatProperty("FloatValueHolder") {
+            @Override
+            public Float get(Object object) {
+                return floatValueHolder.getValue();
+            }
+
+            @Override
+            public void setValue(Object object, float value) {
+                floatValueHolder.setValue(value);
+            }
+        };
+        mMinVisibleChange = MIN_VISIBLE_CHANGE_PIXELS;
+    }
+
+    /**
+     * Creates a dynamic animation to animate the given property for the given {@link View}
+     *
+     * @param object the Object whose property is to be animated
+     * @param property the property to be animated
+     */
+
+    <K> DynamicAnimation(K object, FloatProperty<K> property) {
+        mTarget = object;
+        mProperty = property;
+        if (mProperty == ROTATION || mProperty == ROTATION_X
+                || mProperty == ROTATION_Y) {
+            mMinVisibleChange = MIN_VISIBLE_CHANGE_ROTATION_DEGREES;
+        } else if (mProperty == ALPHA) {
+            mMinVisibleChange = MIN_VISIBLE_CHANGE_ALPHA;
+        } else if (mProperty == SCALE_X || mProperty == SCALE_Y) {
+            mMinVisibleChange = MIN_VISIBLE_CHANGE_SCALE;
+        } else {
+            mMinVisibleChange = MIN_VISIBLE_CHANGE_PIXELS;
+        }
+    }
+
+    /**
+     * Sets the start value of the animation. If start value is not set, the animation will get
+     * the current value for the view's property, and use that as the start value.
+     *
+     * @param startValue start value for the animation
+     * @return the Animation whose start value is being set
+     */
+    @SuppressWarnings("unchecked")
+    public T setStartValue(float startValue) {
+        mValue = startValue;
+        mStartValueIsSet = true;
+        return (T) this;
+    }
+
+    /**
+     * Start velocity of the animation. Default velocity is 0. Unit: change in property per
+     * second (e.g. pixels per second, scale/alpha value change per second).
+     *
+     * <p>Note when using a fixed value as the start velocity (as opposed to getting the velocity
+     * through touch events), it is recommended to define such a value in dp/second and convert it
+     * to pixel/second based on the density of the screen to achieve a consistent look across
+     * different screens.
+     *
+     * <p>To convert from dp/second to pixel/second:
+     * <pre class="prettyprint">
+     * float pixelPerSecond = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond,
+     *         getResources().getDisplayMetrics());
+     * </pre>
+     *
+     * @param startVelocity start velocity of the animation
+     * @return the Animation whose start velocity is being set
+     */
+    @SuppressWarnings("unchecked")
+    public T setStartVelocity(float startVelocity) {
+        mVelocity = startVelocity;
+        return (T) this;
+    }
+
+    /**
+     * Sets the max value of the animation. Animations will not animate beyond their max value.
+     * Whether or not animation will come to an end when max value is reached is dependent on the
+     * child animation's implementation.
+     *
+     * @param max maximum value of the property to be animated
+     * @return the Animation whose max value is being set
+     */
+    @SuppressWarnings("unchecked")
+    public T setMaxValue(float max) {
+        // This max value should be checked and handled in the subclass animations, instead of
+        // assuming the end of the animations when the max/min value is hit in the base class.
+        // The reason is that hitting max/min value may just be a transient state, such as during
+        // the spring oscillation.
+        mMaxValue = max;
+        return (T) this;
+    }
+
+    /**
+     * Sets the min value of the animation. Animations will not animate beyond their min value.
+     * Whether or not animation will come to an end when min value is reached is dependent on the
+     * child animation's implementation.
+     *
+     * @param min minimum value of the property to be animated
+     * @return the Animation whose min value is being set
+     */
+    @SuppressWarnings("unchecked")
+    public T setMinValue(float min) {
+        mMinValue = min;
+        return (T) this;
+    }
+
+    /**
+     * Adds an end listener to the animation for receiving onAnimationEnd callbacks. If the listener
+     * is {@code null} or has already been added to the list of listeners for the animation, no op.
+     *
+     * @param listener the listener to be added
+     * @return the animation to which the listener is added
+     */
+    @SuppressWarnings("unchecked")
+    public T addEndListener(OnAnimationEndListener listener) {
+        if (!mEndListeners.contains(listener)) {
+            mEndListeners.add(listener);
+        }
+        return (T) this;
+    }
+
+    /**
+     * Removes the end listener from the animation, so as to stop receiving animation end callbacks.
+     *
+     * @param listener the listener to be removed
+     */
+    public void removeEndListener(OnAnimationEndListener listener) {
+        removeEntry(mEndListeners, listener);
+    }
+
+    /**
+     * Adds an update listener to the animation for receiving per-frame animation update callbacks.
+     * If the listener is {@code null} or has already been added to the list of listeners for the
+     * animation, no op.
+     *
+     * <p>Note that update listener should only be added before the start of the animation.
+     *
+     * @param listener the listener to be added
+     * @return the animation to which the listener is added
+     * @throws UnsupportedOperationException if the update listener is added after the animation has
+     *                                       started
+     */
+    @SuppressWarnings("unchecked")
+    public T addUpdateListener(OnAnimationUpdateListener listener) {
+        if (isRunning()) {
+            // Require update listener to be added before the animation, such as when we start
+            // the animation, we know whether the animation is RenderThread compatible.
+            throw new UnsupportedOperationException("Error: Update listeners must be added before"
+                    + "the animation.");
+        }
+        if (!mUpdateListeners.contains(listener)) {
+            mUpdateListeners.add(listener);
+        }
+        return (T) this;
+    }
+
+    /**
+     * Removes the update listener from the animation, so as to stop receiving animation update
+     * callbacks.
+     *
+     * @param listener the listener to be removed
+     */
+    public void removeUpdateListener(OnAnimationUpdateListener listener) {
+        removeEntry(mUpdateListeners, listener);
+    }
+
+
+    /**
+     * This method sets the minimal change of animation value that is visible to users, which helps
+     * determine a reasonable threshold for the animation's termination condition. It is critical
+     * to set the minimal visible change for custom properties (i.e. non-<code>ViewProperty</code>s)
+     * unless the custom property is in pixels.
+     *
+     * <p>For custom properties, this minimum visible change defaults to change in pixel
+     * (i.e. {@link #MIN_VISIBLE_CHANGE_PIXELS}. It is recommended to adjust this value that is
+     * reasonable for the property to be animated. A general rule of thumb to calculate such a value
+     * is: minimum visible change = range of custom property value / corresponding pixel range. For
+     * example, if the property to be animated is a progress (from 0 to 100) that corresponds to a
+     * 200-pixel change. Then the min visible change should be 100 / 200. (i.e. 0.5).
+     *
+     * <p>It's not necessary to call this method when animating {@link ViewProperty}s, as the
+     * minimum visible change will be derived from the property. For example, if the property to be
+     * animated is in pixels (i.e. {@link #TRANSLATION_X}, {@link #TRANSLATION_Y},
+     * {@link #TRANSLATION_Z}, @{@link #SCROLL_X} or {@link #SCROLL_Y}), the default minimum visible
+     * change is 1 (pixel). For {@link #ROTATION}, {@link #ROTATION_X} or {@link #ROTATION_Y}, the
+     * animation will use {@link #MIN_VISIBLE_CHANGE_ROTATION_DEGREES} as the min visible change,
+     * which is 1/10. Similarly, the minimum visible change for alpha (
+     * i.e. {@link #MIN_VISIBLE_CHANGE_ALPHA} is defined as 1 / 256.
+     *
+     * @param minimumVisibleChange minimum change in property value that is visible to users
+     * @return the animation whose min visible change is being set
+     * @throws IllegalArgumentException if the given threshold is not positive
+     */
+    @SuppressWarnings("unchecked")
+    public T setMinimumVisibleChange(@FloatRange(from = 0.0, fromInclusive = false)
+            float minimumVisibleChange) {
+        if (minimumVisibleChange <= 0) {
+            throw new IllegalArgumentException("Minimum visible change must be positive.");
+        }
+        mMinVisibleChange = minimumVisibleChange;
+        setValueThreshold(minimumVisibleChange * THRESHOLD_MULTIPLIER);
+        return (T) this;
+    }
+
+    /**
+     * Returns the minimum change in the animation property that could be visibly different to
+     * users.
+     *
+     * @return minimum change in property value that is visible to users
+     */
+    public float getMinimumVisibleChange() {
+        return mMinVisibleChange;
+    }
+
+    /**
+     * Remove {@code null} entries from the list.
+     */
+    private static <T> void removeNullEntries(ArrayList<T> list) {
+        // Clean up null entries
+        for (int i = list.size() - 1; i >= 0; i--) {
+            if (list.get(i) == null) {
+                list.remove(i);
+            }
+        }
+    }
+
+    /**
+     * Remove an entry from the list by marking it {@code null} and clean up later.
+     */
+    private static <T> void removeEntry(ArrayList<T> list, T entry) {
+        int id = list.indexOf(entry);
+        if (id >= 0) {
+            list.set(id, null);
+        }
+    }
+
+    /****************Animation Lifecycle Management***************/
+
+    /**
+     * Starts an animation. If the animation has already been started, no op. Note that calling
+     * {@link #start()} will not immediately set the property value to start value of the animation.
+     * The property values will be changed at each animation pulse, which happens before the draw
+     * pass. As a result, the changes will be reflected in the next frame, the same as if the values
+     * were set immediately. This method should only be called on main thread.
+     *
+     * Unless a AnimationHandler is provided via setAnimationHandler, a default AnimationHandler
+     * is created on the same thread as the first call to start/cancel an animation. All the
+     * subsequent animation lifecycle manipulations need to be on that same thread, until the
+     * AnimationHandler is reset (using [setAnimationHandler]).
+     *
+     * @throws AndroidRuntimeException if this method is not called on the same thread as the
+     * animation handler
+     */
+    @MainThread
+    public void start() {
+        if (!isCurrentThread()) {
+            throw new AndroidRuntimeException("Animations may only be started on the same thread "
+                    + "as the animation handler");
+        }
+        if (!mRunning) {
+            startAnimationInternal();
+        }
+    }
+
+    boolean isCurrentThread() {
+        return Thread.currentThread() == Looper.myLooper().getThread();
+    }
+
+    /**
+     * Cancels the on-going animation. If the animation hasn't started, no op.
+     *
+     * Unless a AnimationHandler is provided via setAnimationHandler, a default AnimationHandler
+     * is created on the same thread as the first call to start/cancel an animation. All the
+     * subsequent animation lifecycle manipulations need to be on that same thread, until the
+     * AnimationHandler is reset (using [setAnimationHandler]).
+     *
+     * @throws AndroidRuntimeException if this method is not called on the same thread as the
+     * animation handler
+     */
+    @MainThread
+    public void cancel() {
+        if (!isCurrentThread()) {
+            throw new AndroidRuntimeException("Animations may only be canceled from the same "
+                    + "thread as the animation handler");
+        }
+        if (mRunning) {
+            endAnimationInternal(true);
+        }
+    }
+
+    /**
+     * Returns whether the animation is currently running.
+     *
+     * @return {@code true} if the animation is currently running, {@code false} otherwise
+     */
+    public boolean isRunning() {
+        return mRunning;
+    }
+
+    /************************** Private APIs below ********************************/
+
+    // This gets called when the animation is started, to finish the setup of the animation
+    // before the animation pulsing starts.
+    private void startAnimationInternal() {
+        if (!mRunning) {
+            mRunning = true;
+            if (!mStartValueIsSet) {
+                mValue = getPropertyValue();
+            }
+            // Sanity check:
+            if (mValue > mMaxValue || mValue < mMinValue) {
+                throw new IllegalArgumentException("Starting value need to be in between min"
+                        + " value and max value");
+            }
+            getAnimationHandler().addAnimationFrameCallback(this, 0);
+        }
+    }
+
+    /**
+     * This gets call on each frame of the animation. Animation value and velocity are updated
+     * in this method based on the new frame time. The property value of the view being animated
+     * is then updated. The animation's ending conditions are also checked in this method. Once
+     * the animation reaches equilibrium, the animation will come to its end, and end listeners
+     * will be notified, if any.
+     */
+    @Override
+    public boolean doAnimationFrame(long frameTime) {
+        if (mLastFrameTime == 0) {
+            // First frame.
+            mLastFrameTime = frameTime;
+            setPropertyValue(mValue);
+            return false;
+        }
+        long deltaT = frameTime - mLastFrameTime;
+        mLastFrameTime = frameTime;
+        float durationScale = ValueAnimator.getDurationScale();
+        deltaT = durationScale == 0.0f ? Integer.MAX_VALUE : (long) (deltaT / durationScale);
+        boolean finished = updateValueAndVelocity(deltaT);
+        // Clamp value & velocity.
+        mValue = Math.min(mValue, mMaxValue);
+        mValue = Math.max(mValue, mMinValue);
+
+        setPropertyValue(mValue);
+
+        if (finished) {
+            endAnimationInternal(false);
+        }
+        return finished;
+    }
+
+    @Override
+    public void commitAnimationFrame(long frameTime) {
+        doAnimationFrame(frameTime);
+    }
+
+    /**
+     * Updates the animation state (i.e. value and velocity). This method is package private, so
+     * subclasses can override this method to calculate the new value and velocity in their custom
+     * way.
+     *
+     * @param deltaT time elapsed in millisecond since last frame
+     * @return whether the animation has finished
+     */
+    abstract boolean updateValueAndVelocity(long deltaT);
+
+    /**
+     * Internal method to reset the animation states when animation is finished/canceled.
+     */
+    private void endAnimationInternal(boolean canceled) {
+        mRunning = false;
+        getAnimationHandler().removeCallback(this);
+        mLastFrameTime = 0;
+        mStartValueIsSet = false;
+        for (int i = 0; i < mEndListeners.size(); i++) {
+            if (mEndListeners.get(i) != null) {
+                mEndListeners.get(i).onAnimationEnd(this, canceled, mValue, mVelocity);
+            }
+        }
+        removeNullEntries(mEndListeners);
+    }
+
+    /**
+     * Updates the property value through the corresponding setter.
+     */
+    @SuppressWarnings("unchecked")
+    void setPropertyValue(float value) {
+        mProperty.setValue(mTarget, value);
+        for (int i = 0; i < mUpdateListeners.size(); i++) {
+            if (mUpdateListeners.get(i) != null) {
+                mUpdateListeners.get(i).onAnimationUpdate(this, mValue, mVelocity);
+            }
+        }
+        removeNullEntries(mUpdateListeners);
+    }
+
+    /**
+     * Returns the default threshold.
+     */
+    float getValueThreshold() {
+        return mMinVisibleChange * THRESHOLD_MULTIPLIER;
+    }
+
+    /**
+     * Obtain the property value through the corresponding getter.
+     */
+    @SuppressWarnings("unchecked")
+    private float getPropertyValue() {
+        return (Float) mProperty.get(mTarget);
+    }
+
+    /**
+     * Returns the {@link AnimationHandler} used to schedule updates for this animator.
+     *
+     * @return the {@link AnimationHandler} for this animator.
+     */
+    @NonNull
+    public AnimationHandler getAnimationHandler() {
+        return mAnimationHandler != null ? mAnimationHandler : AnimationHandler.getInstance();
+    }
+
+    /****************Sub class animations**************/
+    /**
+     * Returns the acceleration at the given value with the given velocity.
+     **/
+    abstract float getAcceleration(float value, float velocity);
+
+    /**
+     * Returns whether the animation has reached equilibrium.
+     */
+    abstract boolean isAtEquilibrium(float value, float velocity);
+
+    /**
+     * Updates the default value threshold for the animation based on the property to be animated.
+     */
+    abstract void setValueThreshold(float threshold);
+
+    /**
+     * An animation listener that receives end notifications from an animation.
+     */
+    public interface OnAnimationEndListener {
+        /**
+         * Notifies the end of an animation. Note that this callback will be invoked not only when
+         * an animation reach equilibrium, but also when the animation is canceled.
+         *
+         * @param animation animation that has ended or was canceled
+         * @param canceled whether the animation has been canceled
+         * @param value the final value when the animation stopped
+         * @param velocity the final velocity when the animation stopped
+         */
+        void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
+                float velocity);
+    }
+
+    /**
+     * Implementors of this interface can add themselves as update listeners
+     * to an <code>DynamicAnimation</code> instance to receive callbacks on every animation
+     * frame, after the current frame's values have been calculated for that
+     * <code>DynamicAnimation</code>.
+     */
+    public interface OnAnimationUpdateListener {
+
+        /**
+         * Notifies the occurrence of another frame of the animation.
+         *
+         * @param animation animation that the update listener is added to
+         * @param value the current value of the animation
+         * @param velocity the current velocity of the animation
+         */
+        void onAnimationUpdate(DynamicAnimation animation, float value, float velocity);
+    }
+}
diff --git a/core/java/com/android/internal/dynamicanimation/animation/FloatValueHolder.java b/core/java/com/android/internal/dynamicanimation/animation/FloatValueHolder.java
new file mode 100644
index 0000000..c3a2cac
--- /dev/null
+++ b/core/java/com/android/internal/dynamicanimation/animation/FloatValueHolder.java
@@ -0,0 +1,64 @@
+/*
+ * 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.internal.dynamicanimation.animation;
+
+/**
+ * <p>FloatValueHolder holds a float value. FloatValueHolder provides a setter and a getter (
+ * i.e. {@link #setValue(float)} and {@link #getValue()}) to access this float value. Animations can
+ * be performed on a FloatValueHolder instance. During each frame of the animation, the
+ * FloatValueHolder will have its value updated via {@link #setValue(float)}. The caller can
+ * obtain the up-to-date animation value via {@link FloatValueHolder#getValue()}.
+ *
+ * @see SpringAnimation#SpringAnimation(FloatValueHolder)
+ */
+
+public class FloatValueHolder {
+    private float mValue = 0.0f;
+
+    /**
+     * Constructs a holder for a float value that is initialized to 0.
+     */
+    public FloatValueHolder() {
+    }
+
+    /**
+     * Constructs a holder for a float value that is initialized to the input value.
+     *
+     * @param value the value to initialize the value held in the FloatValueHolder
+     */
+    public FloatValueHolder(float value) {
+        setValue(value);
+    }
+
+    /**
+     * Sets the value held in the FloatValueHolder instance.
+     *
+     * @param value float value held in the FloatValueHolder instance
+     */
+    public void setValue(float value) {
+        mValue = value;
+    }
+
+    /**
+     * Returns the float value held in the FloatValueHolder instance.
+     *
+     * @return float value held in the FloatValueHolder instance
+     */
+    public float getValue() {
+        return mValue;
+    }
+}
diff --git a/core/java/com/android/internal/dynamicanimation/animation/Force.java b/core/java/com/android/internal/dynamicanimation/animation/Force.java
new file mode 100644
index 0000000..fcb9c45
--- /dev/null
+++ b/core/java/com/android/internal/dynamicanimation/animation/Force.java
@@ -0,0 +1,27 @@
+/*
+ * 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.internal.dynamicanimation.animation;
+
+/**
+ * Hide this for now, in case we want to change the API.
+ */
+interface Force {
+    // Acceleration based on position.
+    float getAcceleration(float position, float velocity);
+
+    boolean isAtEquilibrium(float value, float velocity);
+}
diff --git a/core/java/com/android/internal/dynamicanimation/animation/SpringAnimation.java b/core/java/com/android/internal/dynamicanimation/animation/SpringAnimation.java
new file mode 100644
index 0000000..2f3b72c
--- /dev/null
+++ b/core/java/com/android/internal/dynamicanimation/animation/SpringAnimation.java
@@ -0,0 +1,314 @@
+/*
+ * 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.internal.dynamicanimation.animation;
+
+import android.util.AndroidRuntimeException;
+import android.util.FloatProperty;
+
+/**
+ * SpringAnimation is an animation that is driven by a {@link SpringForce}. The spring force defines
+ * the spring's stiffness, damping ratio, as well as the rest position. Once the SpringAnimation is
+ * started, on each frame the spring force will update the animation's value and velocity.
+ * The animation will continue to run until the spring force reaches equilibrium. If the spring used
+ * in the animation is undamped, the animation will never reach equilibrium. Instead, it will
+ * oscillate forever.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * </div>
+ *
+ * <p>To create a simple {@link SpringAnimation} that uses the default {@link SpringForce}:</p>
+ * <pre class="prettyprint">
+ * // Create an animation to animate view's X property, set the rest position of the
+ * // default spring to 0, and start the animation with a starting velocity of 5000 (pixel/s).
+ * final SpringAnimation anim = new SpringAnimation(view, DynamicAnimation.X, 0)
+ *         .setStartVelocity(5000);
+ * anim.start();
+ * </pre>
+ *
+ * <p>Alternatively, a {@link SpringAnimation} can take a pre-configured {@link SpringForce}, and
+ * use that to drive the animation. </p>
+ * <pre class="prettyprint">
+ * // Create a low stiffness, low bounce spring at position 0.
+ * SpringForce spring = new SpringForce(0)
+ *         .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+ *         .setStiffness(SpringForce.STIFFNESS_LOW);
+ * // Create an animation to animate view's scaleY property, and start the animation using
+ * // the spring above and a starting value of 0.5. Additionally, constrain the range of value for
+ * // the animation to be non-negative, effectively preventing any spring overshoot.
+ * final SpringAnimation anim = new SpringAnimation(view, DynamicAnimation.SCALE_Y)
+ *         .setMinValue(0).setSpring(spring).setStartValue(1);
+ * anim.start();
+ * </pre>
+ */
+public final class SpringAnimation extends DynamicAnimation<SpringAnimation> {
+
+    private SpringForce mSpring = null;
+    private float mPendingPosition = UNSET;
+    private static final float UNSET = Float.MAX_VALUE;
+    private boolean mEndRequested = false;
+
+    /**
+     * <p>This creates a SpringAnimation that animates a {@link FloatValueHolder} instance. During
+     * the animation, the {@link FloatValueHolder} instance will be updated via
+     * {@link FloatValueHolder#setValue(float)} each frame. The caller can obtain the up-to-date
+     * animation value via {@link FloatValueHolder#getValue()}.
+     *
+     * <p><strong>Note:</strong> changing the value in the {@link FloatValueHolder} via
+     * {@link FloatValueHolder#setValue(float)} outside of the animation during an
+     * animation run will not have any effect on the on-going animation.
+     *
+     * @param floatValueHolder the property to be animated
+     */
+    public SpringAnimation(FloatValueHolder floatValueHolder) {
+        super(floatValueHolder);
+    }
+
+    /**
+     * <p>This creates a SpringAnimation that animates a {@link FloatValueHolder} instance. During
+     * the animation, the {@link FloatValueHolder} instance will be updated via
+     * {@link FloatValueHolder#setValue(float)} each frame. The caller can obtain the up-to-date
+     * animation value via {@link FloatValueHolder#getValue()}.
+     *
+     * A Spring will be created with the given final position and default stiffness and damping
+     * ratio. This spring can be accessed and reconfigured through {@link #setSpring(SpringForce)}.
+     *
+     * <p><strong>Note:</strong> changing the value in the {@link FloatValueHolder} via
+     * {@link FloatValueHolder#setValue(float)} outside of the animation during an
+     * animation run will not have any effect on the on-going animation.
+     *
+     * @param floatValueHolder the property to be animated
+     * @param finalPosition the final position of the spring to be created.
+     */
+    public SpringAnimation(FloatValueHolder floatValueHolder, float finalPosition) {
+        super(floatValueHolder);
+        mSpring = new SpringForce(finalPosition);
+    }
+
+    /**
+     * This creates a SpringAnimation that animates the property of the given object.
+     * Note, a spring will need to setup through {@link #setSpring(SpringForce)} before
+     * the animation starts.
+     *
+     * @param object the Object whose property will be animated
+     * @param property the property to be animated
+     * @param <K> the class on which the Property is declared
+     */
+    public <K> SpringAnimation(K object, FloatProperty<K> property) {
+        super(object, property);
+    }
+
+    /**
+     * This creates a SpringAnimation that animates the property of the given object. A Spring will
+     * be created with the given final position and default stiffness and damping ratio.
+     * This spring can be accessed and reconfigured through {@link #setSpring(SpringForce)}.
+     *
+     * @param object the Object whose property will be animated
+     * @param property the property to be animated
+     * @param finalPosition the final position of the spring to be created.
+     * @param <K> the class on which the Property is declared
+     */
+    public <K> SpringAnimation(K object, FloatProperty<K> property,
+            float finalPosition) {
+        super(object, property);
+        mSpring = new SpringForce(finalPosition);
+    }
+
+    /**
+     * Returns the spring that the animation uses for animations.
+     *
+     * @return the spring that the animation uses for animations
+     */
+    public SpringForce getSpring() {
+        return mSpring;
+    }
+
+    /**
+     * Uses the given spring as the force that drives this animation. If this spring force has its
+     * parameters re-configured during the animation, the new configuration will be reflected in the
+     * animation immediately.
+     *
+     * @param force a pre-defined spring force that drives the animation
+     * @return the animation that the spring force is set on
+     */
+    public SpringAnimation setSpring(SpringForce force) {
+        mSpring = force;
+        return this;
+    }
+
+    @Override
+    public void start() {
+        sanityCheck();
+        mSpring.setValueThreshold(getValueThreshold());
+        super.start();
+    }
+
+    /**
+     * Updates the final position of the spring.
+     * <p/>
+     * When the animation is running, calling this method would assume the position change of the
+     * spring as a continuous movement since last frame, which yields more accurate results than
+     * changing the spring position directly through {@link SpringForce#setFinalPosition(float)}.
+     * <p/>
+     * If the animation hasn't started, calling this method will change the spring position, and
+     * immediately start the animation.
+     *
+     * @param finalPosition rest position of the spring
+     */
+    public void animateToFinalPosition(float finalPosition) {
+        if (isRunning()) {
+            mPendingPosition = finalPosition;
+        } else {
+            if (mSpring == null) {
+                mSpring = new SpringForce(finalPosition);
+            }
+            mSpring.setFinalPosition(finalPosition);
+            start();
+        }
+    }
+
+    /**
+     * Cancels the on-going animation. If the animation hasn't started, no op. Note that this method
+     * should only be called on main thread.
+     *
+     * @throws AndroidRuntimeException if this method is not called on the main thread
+     */
+    @Override
+    public void cancel() {
+        super.cancel();
+        if (mPendingPosition != UNSET) {
+            if (mSpring == null) {
+                mSpring = new SpringForce(mPendingPosition);
+            } else {
+                mSpring.setFinalPosition(mPendingPosition);
+            }
+            mPendingPosition = UNSET;
+        }
+    }
+
+    /**
+     * Skips to the end of the animation. If the spring is undamped, an
+     * {@link IllegalStateException} will be thrown, as the animation would never reach to an end.
+     * It is recommended to check {@link #canSkipToEnd()} before calling this method. If animation
+     * is not running, no-op.
+     *
+     * Unless a AnimationHandler is provided via setAnimationHandler, a default AnimationHandler
+     * is created on the same thread as the first call to start/cancel an animation. All the
+     * subsequent animation lifecycle manipulations need to be on that same thread, until the
+     * AnimationHandler is reset (using [setAnimationHandler]).
+     *
+     * @throws IllegalStateException if the spring is undamped (i.e. damping ratio = 0)
+     * @throws AndroidRuntimeException if this method is not called on the same thread as the
+     * animation handler
+     */
+    public void skipToEnd() {
+        if (!canSkipToEnd()) {
+            throw new UnsupportedOperationException("Spring animations can only come to an end"
+                    + " when there is damping");
+        }
+        if (!isCurrentThread()) {
+            throw new AndroidRuntimeException("Animations may only be started on the same thread "
+                    + "as the animation handler");
+        }
+        if (mRunning) {
+            mEndRequested = true;
+        }
+    }
+
+    /**
+     * Queries whether the spring can eventually come to the rest position.
+     *
+     * @return {@code true} if the spring is damped, otherwise {@code false}
+     */
+    public boolean canSkipToEnd() {
+        return mSpring.mDampingRatio > 0;
+    }
+
+    /************************ Below are private APIs *************************/
+
+    private void sanityCheck() {
+        if (mSpring == null) {
+            throw new UnsupportedOperationException("Incomplete SpringAnimation: Either final"
+                    + " position or a spring force needs to be set.");
+        }
+        double finalPosition = mSpring.getFinalPosition();
+        if (finalPosition > mMaxValue) {
+            throw new UnsupportedOperationException("Final position of the spring cannot be greater"
+                    + " than the max value.");
+        } else if (finalPosition < mMinValue) {
+            throw new UnsupportedOperationException("Final position of the spring cannot be less"
+                    + " than the min value.");
+        }
+    }
+
+    @Override
+    boolean updateValueAndVelocity(long deltaT) {
+        // If user had requested end, then update the value and velocity to end state and consider
+        // animation done.
+        if (mEndRequested) {
+            if (mPendingPosition != UNSET) {
+                mSpring.setFinalPosition(mPendingPosition);
+                mPendingPosition = UNSET;
+            }
+            mValue = mSpring.getFinalPosition();
+            mVelocity = 0;
+            mEndRequested = false;
+            return true;
+        }
+
+        if (mPendingPosition != UNSET) {
+            // Approximate by considering half of the time spring position stayed at the old
+            // position, half of the time it's at the new position.
+            MassState massState = mSpring.updateValues(mValue, mVelocity, deltaT / 2);
+            mSpring.setFinalPosition(mPendingPosition);
+            mPendingPosition = UNSET;
+
+            massState = mSpring.updateValues(massState.mValue, massState.mVelocity, deltaT / 2);
+            mValue = massState.mValue;
+            mVelocity = massState.mVelocity;
+
+        } else {
+            MassState massState = mSpring.updateValues(mValue, mVelocity, deltaT);
+            mValue = massState.mValue;
+            mVelocity = massState.mVelocity;
+        }
+
+        mValue = Math.max(mValue, mMinValue);
+        mValue = Math.min(mValue, mMaxValue);
+
+        if (isAtEquilibrium(mValue, mVelocity)) {
+            mValue = mSpring.getFinalPosition();
+            mVelocity = 0f;
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    float getAcceleration(float value, float velocity) {
+        return mSpring.getAcceleration(value, velocity);
+    }
+
+    @Override
+    boolean isAtEquilibrium(float value, float velocity) {
+        return mSpring.isAtEquilibrium(value, velocity);
+    }
+
+    @Override
+    void setValueThreshold(float threshold) {
+    }
+}
diff --git a/core/java/com/android/internal/dynamicanimation/animation/SpringForce.java b/core/java/com/android/internal/dynamicanimation/animation/SpringForce.java
new file mode 100644
index 0000000..36242ae2
--- /dev/null
+++ b/core/java/com/android/internal/dynamicanimation/animation/SpringForce.java
@@ -0,0 +1,323 @@
+/*
+ * 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.internal.dynamicanimation.animation;
+
+import android.annotation.FloatRange;
+
+/**
+ * Spring Force defines the characteristics of the spring being used in the animation.
+ * <p>
+ * By configuring the stiffness and damping ratio, callers can create a spring with the look and
+ * feel suits their use case. Stiffness corresponds to the spring constant. The stiffer the spring
+ * is, the harder it is to stretch it, the faster it undergoes dampening.
+ * <p>
+ * Spring damping ratio describes how oscillations in a system decay after a disturbance.
+ * When damping ratio > 1* (i.e. over-damped), the object will quickly return to the rest position
+ * without overshooting. If damping ratio equals to 1 (i.e. critically damped), the object will
+ * return to equilibrium within the shortest amount of time. When damping ratio is less than 1
+ * (i.e. under-damped), the mass tends to overshoot, and return, and overshoot again. Without any
+ * damping (i.e. damping ratio = 0), the mass will oscillate forever.
+ */
+public final class SpringForce implements Force {
+    /**
+     * Stiffness constant for extremely stiff spring.
+     */
+    public static final float STIFFNESS_HIGH = 10_000f;
+    /**
+     * Stiffness constant for medium stiff spring. This is the default stiffness for spring force.
+     */
+    public static final float STIFFNESS_MEDIUM = 1500f;
+    /**
+     * Stiffness constant for a spring with low stiffness.
+     */
+    public static final float STIFFNESS_LOW = 200f;
+    /**
+     * Stiffness constant for a spring with very low stiffness.
+     */
+    public static final float STIFFNESS_VERY_LOW = 50f;
+
+    /**
+     * Damping ratio for a very bouncy spring. Note for under-damped springs
+     * (i.e. damping ratio < 1), the lower the damping ratio, the more bouncy the spring.
+     */
+    public static final float DAMPING_RATIO_HIGH_BOUNCY = 0.2f;
+    /**
+     * Damping ratio for a medium bouncy spring. This is also the default damping ratio for spring
+     * force. Note for under-damped springs (i.e. damping ratio < 1), the lower the damping ratio,
+     * the more bouncy the spring.
+     */
+    public static final float DAMPING_RATIO_MEDIUM_BOUNCY = 0.5f;
+    /**
+     * Damping ratio for a spring with low bounciness. Note for under-damped springs
+     * (i.e. damping ratio < 1), the lower the damping ratio, the higher the bounciness.
+     */
+    public static final float DAMPING_RATIO_LOW_BOUNCY = 0.75f;
+    /**
+     * Damping ratio for a spring with no bounciness. This damping ratio will create a critically
+     * damped spring that returns to equilibrium within the shortest amount of time without
+     * oscillating.
+     */
+    public static final float DAMPING_RATIO_NO_BOUNCY = 1f;
+
+    // This multiplier is used to calculate the velocity threshold given a certain value threshold.
+    // The idea is that if it takes >= 1 frame to move the value threshold amount, then the velocity
+    // is a reasonable threshold.
+    private static final double VELOCITY_THRESHOLD_MULTIPLIER = 1000.0 / 16.0;
+
+    // Natural frequency
+    double mNaturalFreq = Math.sqrt(STIFFNESS_MEDIUM);
+    // Damping ratio.
+    double mDampingRatio = DAMPING_RATIO_MEDIUM_BOUNCY;
+
+    // Value to indicate an unset state.
+    private static final double UNSET = Double.MAX_VALUE;
+
+    // Indicates whether the spring has been initialized
+    private boolean mInitialized = false;
+
+    // Threshold for velocity and value to determine when it's reasonable to assume that the spring
+    // is approximately at rest.
+    private double mValueThreshold;
+    private double mVelocityThreshold;
+
+    // Intermediate values to simplify the spring function calculation per frame.
+    private double mGammaPlus;
+    private double mGammaMinus;
+    private double mDampedFreq;
+
+    // Final position of the spring. This must be set before the start of the animation.
+    private double mFinalPosition = UNSET;
+
+    // Internal state to hold a value/velocity pair.
+    private final DynamicAnimation.MassState mMassState = new DynamicAnimation.MassState();
+
+    /**
+     * Creates a spring force. Note that final position of the spring must be set through
+     * {@link #setFinalPosition(float)} before the spring animation starts.
+     */
+    public SpringForce() {
+        // No op.
+    }
+
+    /**
+     * Creates a spring with a given final rest position.
+     *
+     * @param finalPosition final position of the spring when it reaches equilibrium
+     */
+    public SpringForce(float finalPosition) {
+        mFinalPosition = finalPosition;
+    }
+
+    /**
+     * Sets the stiffness of a spring. The more stiff a spring is, the more force it applies to
+     * the object attached when the spring is not at the final position. Default stiffness is
+     * {@link #STIFFNESS_MEDIUM}.
+     *
+     * @param stiffness non-negative stiffness constant of a spring
+     * @return the spring force that the given stiffness is set on
+     * @throws IllegalArgumentException if the given spring stiffness is not positive
+     */
+    public SpringForce setStiffness(
+            @FloatRange(from = 0.0, fromInclusive = false) float stiffness) {
+        if (stiffness <= 0) {
+            throw new IllegalArgumentException("Spring stiffness constant must be positive.");
+        }
+        mNaturalFreq = Math.sqrt(stiffness);
+        // All the intermediate values need to be recalculated.
+        mInitialized = false;
+        return this;
+    }
+
+    /**
+     * Gets the stiffness of the spring.
+     *
+     * @return the stiffness of the spring
+     */
+    public float getStiffness() {
+        return (float) (mNaturalFreq * mNaturalFreq);
+    }
+
+    /**
+     * Spring damping ratio describes how oscillations in a system decay after a disturbance.
+     * <p>
+     * When damping ratio > 1 (over-damped), the object will quickly return to the rest position
+     * without overshooting. If damping ratio equals to 1 (i.e. critically damped), the object will
+     * return to equilibrium within the shortest amount of time. When damping ratio is less than 1
+     * (i.e. under-damped), the mass tends to overshoot, and return, and overshoot again. Without
+     * any damping (i.e. damping ratio = 0), the mass will oscillate forever.
+     * <p>
+     * Default damping ratio is {@link #DAMPING_RATIO_MEDIUM_BOUNCY}.
+     *
+     * @param dampingRatio damping ratio of the spring, it should be non-negative
+     * @return the spring force that the given damping ratio is set on
+     * @throws IllegalArgumentException if the {@param dampingRatio} is negative.
+     */
+    public SpringForce setDampingRatio(@FloatRange(from = 0.0) float dampingRatio) {
+        if (dampingRatio < 0) {
+            throw new IllegalArgumentException("Damping ratio must be non-negative");
+        }
+        mDampingRatio = dampingRatio;
+        // All the intermediate values need to be recalculated.
+        mInitialized = false;
+        return this;
+    }
+
+    /**
+     * Returns the damping ratio of the spring.
+     *
+     * @return damping ratio of the spring
+     */
+    public float getDampingRatio() {
+        return (float) mDampingRatio;
+    }
+
+    /**
+     * Sets the rest position of the spring.
+     *
+     * @param finalPosition rest position of the spring
+     * @return the spring force that the given final position is set on
+     */
+    public SpringForce setFinalPosition(float finalPosition) {
+        mFinalPosition = finalPosition;
+        return this;
+    }
+
+    /**
+     * Returns the rest position of the spring.
+     *
+     * @return rest position of the spring
+     */
+    public float getFinalPosition() {
+        return (float) mFinalPosition;
+    }
+
+    /*********************** Below are private APIs *********************/
+
+    @Override
+    public float getAcceleration(float lastDisplacement, float lastVelocity) {
+
+        lastDisplacement -= getFinalPosition();
+
+        double k = mNaturalFreq * mNaturalFreq;
+        double c = 2 * mNaturalFreq * mDampingRatio;
+
+        return (float) (-k * lastDisplacement - c * lastVelocity);
+    }
+
+    @Override
+    public boolean isAtEquilibrium(float value, float velocity) {
+        if (Math.abs(velocity) < mVelocityThreshold
+                && Math.abs(value - getFinalPosition()) < mValueThreshold) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Initialize the string by doing the necessary pre-calculation as well as some sanity check
+     * on the setup.
+     *
+     * @throws IllegalStateException if the final position is not yet set by the time the spring
+     *                               animation has started
+     */
+    private void init() {
+        if (mInitialized) {
+            return;
+        }
+
+        if (mFinalPosition == UNSET) {
+            throw new IllegalStateException("Error: Final position of the spring must be"
+                    + " set before the animation starts");
+        }
+
+        if (mDampingRatio > 1) {
+            // Over damping
+            mGammaPlus = -mDampingRatio * mNaturalFreq
+                    + mNaturalFreq * Math.sqrt(mDampingRatio * mDampingRatio - 1);
+            mGammaMinus = -mDampingRatio * mNaturalFreq
+                    - mNaturalFreq * Math.sqrt(mDampingRatio * mDampingRatio - 1);
+        } else if (mDampingRatio >= 0 && mDampingRatio < 1) {
+            // Under damping
+            mDampedFreq = mNaturalFreq * Math.sqrt(1 - mDampingRatio * mDampingRatio);
+        }
+
+        mInitialized = true;
+    }
+
+    /**
+     * Internal only call for Spring to calculate the spring position/velocity using
+     * an analytical approach.
+     */
+    DynamicAnimation.MassState updateValues(double lastDisplacement, double lastVelocity,
+            long timeElapsed) {
+        init();
+
+        double deltaT = timeElapsed / 1000d; // unit: seconds
+        lastDisplacement -= mFinalPosition;
+        double displacement;
+        double currentVelocity;
+        if (mDampingRatio > 1) {
+            // Overdamped
+            double coeffA =  lastDisplacement - (mGammaMinus * lastDisplacement - lastVelocity)
+                    / (mGammaMinus - mGammaPlus);
+            double coeffB =  (mGammaMinus * lastDisplacement - lastVelocity)
+                    / (mGammaMinus - mGammaPlus);
+            displacement = coeffA * Math.pow(Math.E, mGammaMinus * deltaT)
+                    + coeffB * Math.pow(Math.E, mGammaPlus * deltaT);
+            currentVelocity = coeffA * mGammaMinus * Math.pow(Math.E, mGammaMinus * deltaT)
+                    + coeffB * mGammaPlus * Math.pow(Math.E, mGammaPlus * deltaT);
+        } else if (mDampingRatio == 1) {
+            // Critically damped
+            double coeffA = lastDisplacement;
+            double coeffB = lastVelocity + mNaturalFreq * lastDisplacement;
+            displacement = (coeffA + coeffB * deltaT) * Math.pow(Math.E, -mNaturalFreq * deltaT);
+            currentVelocity = (coeffA + coeffB * deltaT) * Math.pow(Math.E, -mNaturalFreq * deltaT)
+                    * -mNaturalFreq + coeffB * Math.pow(Math.E, -mNaturalFreq * deltaT);
+        } else {
+            // Underdamped
+            double cosCoeff = lastDisplacement;
+            double sinCoeff = (1 / mDampedFreq) * (mDampingRatio * mNaturalFreq
+                    * lastDisplacement + lastVelocity);
+            displacement = Math.pow(Math.E, -mDampingRatio * mNaturalFreq * deltaT)
+                    * (cosCoeff * Math.cos(mDampedFreq * deltaT)
+                    + sinCoeff * Math.sin(mDampedFreq * deltaT));
+            currentVelocity = displacement * -mNaturalFreq * mDampingRatio
+                    + Math.pow(Math.E, -mDampingRatio * mNaturalFreq * deltaT)
+                    * (-mDampedFreq * cosCoeff * Math.sin(mDampedFreq * deltaT)
+                    + mDampedFreq * sinCoeff * Math.cos(mDampedFreq * deltaT));
+        }
+
+        mMassState.mValue = (float) (displacement + mFinalPosition);
+        mMassState.mVelocity = (float) currentVelocity;
+        return mMassState;
+    }
+
+    /**
+     * This threshold defines how close the animation value needs to be before the animation can
+     * finish. This default value is based on the property being animated, e.g. animations on alpha,
+     * scale, translation or rotation would have different thresholds. This value should be small
+     * enough to avoid visual glitch of "jumping to the end". But it shouldn't be so small that
+     * animations take seconds to finish.
+     *
+     * @param threshold the difference between the animation value and final spring position that
+     *                  is allowed to end the animation when velocity is very low
+     */
+    void setValueThreshold(double threshold) {
+        mValueThreshold = Math.abs(threshold);
+        mVelocityThreshold = mValueThreshold * VELOCITY_THRESHOLD_MULTIPLIER;
+    }
+}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 5de7240..a05062b 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -45,6 +45,7 @@
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL;
@@ -223,6 +224,7 @@
     public static final int CUJ_SHADE_CLEAR_ALL = 62;
     public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = 63;
     public static final int CUJ_LOCKSCREEN_OCCLUSION = 64;
+    public static final int CUJ_RECENTS_SCROLLING = 65;
 
     private static final int NO_STATSD_LOGGING = -1;
 
@@ -296,6 +298,7 @@
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL,
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING,
     };
 
     private static volatile InteractionJankMonitor sInstance;
@@ -380,7 +383,8 @@
             CUJ_TASKBAR_COLLAPSE,
             CUJ_SHADE_CLEAR_ALL,
             CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
-            CUJ_LOCKSCREEN_OCCLUSION
+            CUJ_LOCKSCREEN_OCCLUSION,
+            CUJ_RECENTS_SCROLLING
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
@@ -893,6 +897,8 @@
                 return "LAUNCHER_UNLOCK_ENTRANCE_ANIMATION";
             case CUJ_LOCKSCREEN_OCCLUSION:
                 return "LOCKSCREEN_OCCLUSION";
+            case CUJ_RECENTS_SCROLLING:
+                return "RECENTS_SCROLLING";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 8b96597..6fed26c 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -1326,6 +1326,13 @@
     LongSamplingCounter mMobileRadioActiveUnknownTime;
     LongSamplingCounter mMobileRadioActiveUnknownCount;
 
+    /**
+     * The soonest the Mobile Radio stats can be updated due to a mobile radio power state change
+     * after it was last updated.
+     */
+    @VisibleForTesting
+    protected static final long MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS = 1000 * 60 * 10;
+
     int mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
 
     @GuardedBy("this")
@@ -6260,6 +6267,15 @@
             } else {
                 mMobileRadioActiveTimer.stopRunningLocked(realElapsedRealtimeMs);
                 mMobileRadioActivePerAppTimer.stopRunningLocked(realElapsedRealtimeMs);
+
+                if (mLastModemActivityInfo != null) {
+                    if (elapsedRealtimeMs < mLastModemActivityInfo.getTimestampMillis()
+                            + MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS) {
+                        // Modem Activity info has been collected recently, don't bother
+                        // triggering another update.
+                        return false;
+                    }
+                }
                 // Tell the caller to collect radio network/power stats.
                 return true;
             }
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 44cfe1a..1d4b246 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -322,4 +322,7 @@
 
     /** Unregisters a nearby media devices provider. */
     void unregisterNearbyMediaDevicesProvider(in INearbyMediaDevicesProvider provider);
+
+    /** Dump protos from SystemUI. The proto definition is defined there */
+    void dumpProto(in String[] args, in ParcelFileDescriptor pfd);
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 964fe2d..f7467b5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1147,7 +1147,28 @@
                 android:protectionLevel="dangerous" />
 
     <!-- Allows an application to write to external storage.
-         <p class="note"><strong>Note:</strong> If <em>both</em> your <a
+         <p><strong>Note: </strong>If your app targets {@link android.os.Build.VERSION_CODES#R} or
+         higher, this permission has no effect.
+
+         <p>If your app is on a device that runs API level 19 or higher, you don't need to declare
+         this permission to read and write files in your application-specific directories returned
+         by {@link android.content.Context#getExternalFilesDir} and
+         {@link android.content.Context#getExternalCacheDir}.
+
+         <p>Learn more about how to
+         <a href="{@docRoot}training/data-storage/shared/media#update-other-apps-files">modify media
+         files</a> that your app doesn't own, and how to
+         <a href="{@docRoot}training/data-storage/shared/documents-files">modify non-media files</a>
+         that your app doesn't own.
+
+         <p>If your app is a file manager and needs broad access to external storage files, then
+         the system must place your app on an allowlist so that you can successfully request the
+         <a href="#MANAGE_EXTERNAL_STORAGE><code>MANAGE_EXTERNAL_STORAGE</code></a> permission.
+         Learn more about the appropriate use cases for
+         <a href="{@docRoot}training/data-storage/manage-all-files>managing all files on a storage
+         device</a>.
+
+         <p>If <em>both</em> your <a
          href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
          minSdkVersion}</a> and <a
          href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
@@ -1155,12 +1176,6 @@
          grants your app this permission. If you don't need this permission, be sure your <a
          href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
          targetSdkVersion}</a> is 4 or higher.
-         <p>Starting in API level 19, this permission is <em>not</em> required to
-         read/write files in your application-specific directories returned by
-         {@link android.content.Context#getExternalFilesDir} and
-         {@link android.content.Context#getExternalCacheDir}.
-         <p>If this permission is not allowlisted for an app that targets an API level before
-         {@link android.os.Build.VERSION_CODES#Q} this permission cannot be granted to apps.</p>
          <p>Protection level: dangerous</p>
     -->
     <permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
diff --git a/core/res/res/anim/dream_activity_close_exit.xml b/core/res/res/anim/dream_activity_close_exit.xml
index c4599da..8df624f 100644
--- a/core/res/res/anim/dream_activity_close_exit.xml
+++ b/core/res/res/anim/dream_activity_close_exit.xml
@@ -19,5 +19,5 @@
 <alpha xmlns:android="http://schemas.android.com/apk/res/android"
     android:fromAlpha="1.0"
     android:toAlpha="0.0"
-    android:duration="100" />
+    android:duration="@integer/config_dreamCloseAnimationDuration" />
 
diff --git a/core/res/res/anim/dream_activity_open_enter.xml b/core/res/res/anim/dream_activity_open_enter.xml
index 9e1c6e2..d6d9c5c 100644
--- a/core/res/res/anim/dream_activity_open_enter.xml
+++ b/core/res/res/anim/dream_activity_open_enter.xml
@@ -22,5 +22,5 @@
 <alpha xmlns:android="http://schemas.android.com/apk/res/android"
     android:fromAlpha="0.0"
     android:toAlpha="1.0"
-    android:duration="1000" />
+    android:duration="@integer/config_dreamOpenAnimationDuration" />
 
diff --git a/core/res/res/anim/dream_activity_open_exit.xml b/core/res/res/anim/dream_activity_open_exit.xml
index 740f528..2c2e501 100644
--- a/core/res/res/anim/dream_activity_open_exit.xml
+++ b/core/res/res/anim/dream_activity_open_exit.xml
@@ -22,4 +22,4 @@
 <alpha xmlns:android="http://schemas.android.com/apk/res/android"
     android:fromAlpha="1.0"
     android:toAlpha="1.0"
-    android:duration="1000" />
+    android:duration="@integer/config_dreamOpenAnimationDuration" />
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index a7f2aa7..be1c939 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -24,6 +24,7 @@
     android:gravity="center_vertical"
     android:orientation="horizontal"
     android:theme="@style/Theme.DeviceDefault.Notification"
+    android:importantForAccessibility="no"
     >
 
     <ImageView
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 7c439008..46f0d81 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -178,7 +178,7 @@
     <string name="contentServiceTooManyDeletesNotificationDesc" msgid="4562226280528716090">"በጣም ብዙ <xliff:g id="CONTENT_TYPE">%s</xliff:g> ለመሰረዝ ተሞክሯል።"</string>
     <string name="low_memory" product="tablet" msgid="5557552311566179924">"የጡባዊ ተኮ ማከማቻ ሙሉ ነው! ቦታ ነፃ ለማድረግ አንዳንድ ፋይሎች ሰርዝ።"</string>
     <string name="low_memory" product="watch" msgid="3479447988234030194">"የእጅ ሰዓት ማከማቻ ሙሉ ነው። ቦታ ለማስለቀቅ አንዳንድ ፋይሎችን ይሰርዙ።"</string>
-    <string name="low_memory" product="tv" msgid="6663680413790323318">"Android TV መሣሪያ ማከማቻ ሙሉ ነው። ባዶ ቦታን ነጻ ለማድረግ አንዳንድ ፋይሎችን ይሰርዙ።"</string>
+    <string name="low_memory" product="tv" msgid="6663680413790323318">"Android TV መሣሪያ ማከማቻ ሙሉ ነው። ባዶ ቦታን ነፃ ለማድረግ አንዳንድ ፋይሎችን ይሰርዙ።"</string>
     <string name="low_memory" product="default" msgid="2539532364144025569">"የስልክ ማከማቻ ሙሉ ነው! ቦታ ነፃ ለማድረግ አንዳንድ ፋይሎች ሰርዝ።"</string>
     <string name="ssl_ca_cert_warning" msgid="7233573909730048571">"{count,plural, =1{የእውቅና ማረጋገጫ ባለስልጣን ተጭኗል}one{የእውቅና ማረጋገጫ ባለስልጣናት ተጭነዋል}other{የእውቅና ማረጋገጫ ባለስልጣናት ተጭነዋል}}"</string>
     <string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"ባልታወቀ ሶስተኛ ወገን"</string>
@@ -1156,7 +1156,7 @@
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"የግቤት ስልትን ቀይር"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"የማከማቻ ቦታ እያለቀ ነው"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"አንዳንድ የስርዓት ተግባራት ላይሰሩ ይችላሉ"</string>
-    <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"ለስርዓቱ የሚሆን በቂ ቦታ የለም። 250 ሜባ ነጻ ቦታ እንዳለዎት ያረጋግጡና ዳግም ያስጀምሩ።"</string>
+    <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"ለስርዓቱ የሚሆን በቂ ቦታ የለም። 250 ሜባ ነፃ ቦታ እንዳለዎት ያረጋግጡና ዳግም ያስጀምሩ።"</string>
     <string name="app_running_notification_title" msgid="8985999749231486569">"<xliff:g id="APP_NAME">%1$s</xliff:g> እያሄደ ነው"</string>
     <string name="app_running_notification_text" msgid="5120815883400228566">"ተጨማሪ መረጃ ለማግኘት ወይም መተግበሪያውን ለማቆም መታ ያድርጉ።"</string>
     <string name="ok" msgid="2646370155170753815">"እሺ"</string>
@@ -1211,7 +1211,7 @@
     <string name="aerr_restart" msgid="2789618625210505419">"መተግበሪያውን እንደገና ክፈት"</string>
     <string name="aerr_report" msgid="3095644466849299308">"ግብረመልስ ይላኩ"</string>
     <string name="aerr_close" msgid="3398336821267021852">"ዝጋ"</string>
-    <string name="aerr_mute" msgid="2304972923480211376">"መሣሪያ ዳግም እስኪጀመር ድረስ ድምጽ ያጥፉ"</string>
+    <string name="aerr_mute" msgid="2304972923480211376">"መሣሪያ ዳግም እስኪጀመር ድረስ ድምፅ ያጥፉ"</string>
     <string name="aerr_wait" msgid="3198677780474548217">"ጠብቅ"</string>
     <string name="aerr_close_app" msgid="8318883106083050970">"መተግበሪያን ዝጋ"</string>
     <string name="anr_title" msgid="7290329487067300120"></string>
@@ -1273,7 +1273,7 @@
     <string name="dump_heap_ready_text" msgid="5849618132123045516">"የ<xliff:g id="PROC">%1$s</xliff:g> ሂደት ተራጋፊ ክምር ለማጋራት ለእርስዎ ይገኛል። ይጠንቀቁ፦ ይህ ተራጋፊ ክምር ሂደቱ ሊደርስባቸው የሚችለው ማንኛውም የግል መረጃ ሊኖረው ይችላል፣ ይህ እርስዎ የተየቧቸውን ነገሮች ሊያካትት ይችላል።"</string>
     <string name="sendText" msgid="493003724401350724">"ለፅሁፍ ድርጊት ምረጥ"</string>
     <string name="volume_ringtone" msgid="134784084629229029">"የስልክ ጥሪ ድምፅ"</string>
-    <string name="volume_music" msgid="7727274216734955095">"የማህደረ መረጃ ድምጽ መጠን"</string>
+    <string name="volume_music" msgid="7727274216734955095">"የማህደረ መረጃ ድምፅ መጠን"</string>
     <string name="volume_music_hint_playing_through_bluetooth" msgid="2614142915948898228">"በብሉቱዝ በኩል ማጫወት"</string>
     <string name="volume_music_hint_silent_ringtone_selected" msgid="1514829655029062233">"የፀጥታ የስልክ የደውል ድምፅ ተዘጋጅቷል"</string>
     <string name="volume_call" msgid="7625321655265747433">"የጥሪ ላይ ድም ፅ መጨመሪያ/መቀነሻ"</string>
@@ -1284,7 +1284,7 @@
     <string name="volume_icon_description_bluetooth" msgid="7540388479345558400">"የብሉቱዝ ድምፅ መጠን"</string>
     <string name="volume_icon_description_ringer" msgid="2187800636867423459">"የስልክ ጥሪ ድምፅ መጠን"</string>
     <string name="volume_icon_description_incall" msgid="4491255105381227919">"የስልክ ጥሪ ድምፅ መጠን"</string>
-    <string name="volume_icon_description_media" msgid="4997633254078171233">"የማህደረ መረጃ ድምጽ መጠን"</string>
+    <string name="volume_icon_description_media" msgid="4997633254078171233">"የማህደረ መረጃ ድምፅ መጠን"</string>
     <string name="volume_icon_description_notification" msgid="579091344110747279">"የማሳወቂያ ክፍልፍል"</string>
     <string name="ringtone_default" msgid="9118299121288174597">"ነባሪ የስልክ ላይ ጥሪ"</string>
     <string name="ringtone_default_with_actual" msgid="2709686194556159773">"ነባሪ (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
@@ -1617,7 +1617,7 @@
     <string name="default_audio_route_name_headphones" msgid="6954070994792640762">"የጆሮ ማዳመጫዎች"</string>
     <string name="default_audio_route_name_usb" msgid="895668743163316932">"ዩ ኤስ ቢ"</string>
     <string name="default_audio_route_category_name" msgid="5241740395748134483">"ስርዓት"</string>
-    <string name="bluetooth_a2dp_audio_route_name" msgid="4214648773120426288">"የብሉቱዝ ድምጽ"</string>
+    <string name="bluetooth_a2dp_audio_route_name" msgid="4214648773120426288">"የብሉቱዝ ድምፅ"</string>
     <string name="wireless_display_route_description" msgid="8297563323032966831">"ገመድ አልባ ማሳያ"</string>
     <string name="media_route_button_content_description" msgid="2299223698196869956">"Cast"</string>
     <string name="media_route_chooser_title" msgid="6646594924991269208">"ከመሳሪያ ጋር ያገናኙ"</string>
@@ -1674,7 +1674,7 @@
     <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"የመክፈቻ ስርዓተ ጥለቱን <xliff:g id="NUMBER_0">%1$d</xliff:g> ጊዜ በትክክል አልሳሉትም። ከ<xliff:g id="NUMBER_1">%2$d</xliff:g> ተጨማሪ ያልተሳኩ ሙከራዎች በኋላ የኢሜይል መለያ ተጠቅመው ስልክዎን እንዲከፍቱ ይጠየቃሉ።\n\nእባክዎ ከ<xliff:g id="NUMBER_2">%3$d</xliff:g> ሰከንዶች በኋላ እንደገና ይሞክሩ።"</string>
     <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string>
     <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"አስወግድ"</string>
-    <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ድምጹ ከሚመከረው መጠን በላይ ከፍ ይበል?\n\nበከፍተኛ ድምጽ ለረጅም ጊዜ ማዳመጥ ጆሮዎን ሊጎዳው ይችላል።"</string>
+    <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ድምጹ ከሚመከረው መጠን በላይ ከፍ ይበል?\n\nበከፍተኛ ድምፅ ለረጅም ጊዜ ማዳመጥ ጆሮዎን ሊጎዳው ይችላል።"</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"የተደራሽነት አቋራጭ ጥቅም ላይ ይዋል?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"አቋራጩ ሲበራ ሁለቱንም የድምጽ አዝራሮች ለ3 ሰከንዶች ተጭኖ መቆየት የተደራሽነት ባህሪን ያስጀምረዋል።"</string>
     <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"የተደራሽነት ባህሪዎች አቋራጭ ይብራ?"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 1280485..c2f24a5 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -514,7 +514,7 @@
     <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"للسماح للتطبيق بالحصول على قائمة بالحسابات التي يعرفها الهاتف. وقد يتضمن ذلك أي حسابات تم إنشاؤها بواسطة التطبيقات التي ثبتها."</string>
     <string name="permlab_accessNetworkState" msgid="2349126720783633918">"عرض اتصالات الشبكة"</string>
     <string name="permdesc_accessNetworkState" msgid="4394564702881662849">"للسماح للتطبيق بعرض معلومات حول اتصالات الشبكة كعرض معلومات عن الشبكات المتوفرة والشبكات المتصلة."</string>
-    <string name="permlab_createNetworkSockets" msgid="3224420491603590541">"حق الوصول الكامل إلى الشبكة"</string>
+    <string name="permlab_createNetworkSockets" msgid="3224420491603590541">"الإذن بالوصول الكامل إلى الشبكة"</string>
     <string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"للسماح للتطبيق بإنشاء مقابس شبكات واستخدام بروتوكولات شبكات مخصصة. ويوفر المتصفح وتطبيقات أخرى طرقًا لإرسال البيانات إلى الإنترنت، ولذلك لا يعد هذا الإذن مطلوبًا لإرسال البيانات إلى الإنترنت."</string>
     <string name="permlab_changeNetworkState" msgid="8945711637530425586">"تغيير اتصال الشبكة"</string>
     <string name="permdesc_changeNetworkState" msgid="649341947816898736">"للسماح للتطبيق بتغيير حالة اتصال الشبكة."</string>
@@ -1272,7 +1272,7 @@
     <string name="dump_heap_ready_notification" msgid="2302452262927390268">"نَسْخ الذاكرة <xliff:g id="PROC">%1$s</xliff:g> جاهز"</string>
     <string name="dump_heap_notification_detail" msgid="8431586843001054050">"تم جمع مقدار كبير من بيانات الذاكرة. انقر للمشاركة."</string>
     <string name="dump_heap_title" msgid="4367128917229233901">"هل تريد مشاركة نَسْخ الذاكرة؟"</string>
-    <string name="dump_heap_text" msgid="1692649033835719336">"تجاوزت عملية <xliff:g id="PROC">%1$s</xliff:g> حد الذاكرة المخصص لها وقدره <xliff:g id="SIZE">%2$s</xliff:g>، ويتوفر نَسْخ للذاكرة لمشاركته مع مطور برامج العملية ولكن توخ الحذر حيث قد يحتوي نَسْخ الذاكرة هذا على معلومات شخصية يملك التطبيق حق الوصول إليها."</string>
+    <string name="dump_heap_text" msgid="1692649033835719336">"تجاوزت عملية <xliff:g id="PROC">%1$s</xliff:g> حد الذاكرة المخصص لها وقدره <xliff:g id="SIZE">%2$s</xliff:g>، ويتوفر نَسْخ للذاكرة لمشاركته مع مطور برامج العملية ولكن توخ الحذر حيث قد يحتوي نَسْخ الذاكرة هذا على معلومات شخصية يملك التطبيق الإذن بالوصول إليها."</string>
     <string name="dump_heap_system_text" msgid="6805155514925350849">"تجاوزت عملية <xliff:g id="PROC">%1$s</xliff:g> القيد المفروض على الذاكرة الذي يبلغ <xliff:g id="SIZE">%2$s</xliff:g>. ويتوفّر نَسْخ ذاكرة يمكنك مشاركته. تحذير: قد يحتوي نَسْخ الذاكرة هذا على معلومات شخصية حسّاسة يمكن للعملية الوصول إليها، وقد يتضمن معلومات سبق لك كتابتها."</string>
     <string name="dump_heap_ready_text" msgid="5849618132123045516">"يتوفّر نَسْخ ذاكرة من عملية <xliff:g id="PROC">%1$s</xliff:g> حتى تتمكّن من مشاركته. تحذير: قد يحتوي نَسْخ الذاكرة هذا على معلومات شخصية حسّاسة يمكن للعملية الوصول إليها، وقد يتضمن معلومات سبق لك كتابتها."</string>
     <string name="sendText" msgid="493003724401350724">"اختيار إجراء للنص"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index c9550c8..34cac8b 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -171,7 +171,7 @@
     <string name="httpErrorBadUrl" msgid="754447723314832538">"অমান্য URLৰ বাবে পৃষ্ঠাটো খুলিব পৰা নগ\'ল।"</string>
     <string name="httpErrorFile" msgid="3400658466057744084">"ফাইলত খুলিব পৰা নগ\'ল।"</string>
     <string name="httpErrorFileNotFound" msgid="5191433324871147386">"অনুৰোধ কৰা ফাইলটো বিচাৰি পোৱা নগ\'ল।"</string>
-    <string name="httpErrorTooManyRequests" msgid="2149677715552037198">"বহুত বেছি অনুৰোধৰ প্ৰক্ৰিয়া চলি আছে৷ অনুগ্ৰহ কৰি পিছত আকৌ চেষ্টা কৰক৷"</string>
+    <string name="httpErrorTooManyRequests" msgid="2149677715552037198">"বহুত বেছি অনুৰোধৰ প্ৰক্ৰিয়া চলি আছে৷ অনুগ্ৰহ কৰি পাছত আকৌ চেষ্টা কৰক৷"</string>
     <string name="notification_title" msgid="5783748077084481121">"<xliff:g id="ACCOUNT">%1$s</xliff:g>ত ছাইন ইন কৰাত আসোঁৱাহ"</string>
     <string name="contentServiceSync" msgid="2341041749565687871">"ছিংক ত্ৰুটি"</string>
     <string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"ছিংক কৰিব নোৱাৰি"</string>
@@ -203,7 +203,7 @@
     <string name="device_policy_manager_service" msgid="5085762851388850332">"ডিভাইচৰ নীতিৰ পৰিচালক সেৱা"</string>
     <string name="music_recognition_manager_service" msgid="7481956037950276359">"সংগীত চিনাক্তকৰণ পৰিচালক সেৱা"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"আপোনাৰ ডিভাইচৰ ডেটা মচা হ\'ব"</string>
-    <string name="factory_reset_message" msgid="2657049595153992213">"এই প্ৰশাসক এপটো ব্যৱহাৰ কৰিব নোৱাৰি। এতিয়া আপোনাৰ ডিভাইচটোৰ ডেটা মচা হ\'ব।\n\nআপোনাৰ কিবা প্ৰশ্ন থাকিলে আপোনাৰ প্ৰতিষ্ঠানৰ প্ৰশাসকৰ সৈতে যোগাযোগ কৰক।"</string>
+    <string name="factory_reset_message" msgid="2657049595153992213">"এই প্ৰশাসক এপ্‌টো ব্যৱহাৰ কৰিব নোৱাৰি। এতিয়া আপোনাৰ ডিভাইচটোৰ ডেটা মচা হ\'ব।\n\nআপোনাৰ কিবা প্ৰশ্ন থাকিলে আপোনাৰ প্ৰতিষ্ঠানৰ প্ৰশাসকৰ সৈতে যোগাযোগ কৰক।"</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"প্ৰিণ্ট কৰা কাৰ্য <xliff:g id="OWNER_APP">%s</xliff:g>এ অক্ষম কৰি ৰাখিছে।"</string>
     <string name="personal_apps_suspension_title" msgid="7561416677884286600">"কৰ্মস্থানৰ প্ৰ’ফাইলটো অন কৰক"</string>
     <string name="personal_apps_suspension_text" msgid="6115455688932935597">"আপুনি নিজৰ কৰ্মস্থানৰ প্ৰ’ফাইলটো অন নকৰালৈকে আপোনাৰ ব্যক্তিগত এপ্‌সমূহ অৱৰোধ কৰা থাকে"</string>
@@ -342,23 +342,23 @@
     <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"স্ক্ৰীনশ্বট লওক"</string>
     <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"ডিছপ্লে’খনৰ এটা স্ক্ৰীনশ্বট ল\'ব পাৰে।"</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"স্থিতি দণ্ড অক্ষম কৰক বা সলনি কৰক"</string>
-    <string name="permdesc_statusBar" msgid="5809162768651019642">"স্থিতি দণ্ড অক্ষম কৰিবলৈ বা ছিষ্টেম আইকন আঁতৰাবলৈ এপটোক অনুমতি দিয়ে।"</string>
+    <string name="permdesc_statusBar" msgid="5809162768651019642">"স্থিতি দণ্ড অক্ষম কৰিবলৈ বা ছিষ্টেম আইকন আঁতৰাবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"স্থিতি দণ্ড হ\'ব পাৰে"</string>
-    <string name="permdesc_statusBarService" msgid="6652917399085712557">"নিজকে স্থিতি দণ্ডৰূপে দেখুওৱাবলৈ এপটোক অনুমতি দিয়ে।"</string>
+    <string name="permdesc_statusBarService" msgid="6652917399085712557">"নিজকে স্থিতি দণ্ডৰূপে দেখুওৱাবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permlab_expandStatusBar" msgid="1184232794782141698">"স্থিতি দণ্ড সম্প্ৰসাৰিত বা সংকোচিত কৰক"</string>
-    <string name="permdesc_expandStatusBar" msgid="7180756900448498536">"স্থিতি দণ্ড বিস্তাৰিত বা সংকুচিত কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+    <string name="permdesc_expandStatusBar" msgid="7180756900448498536">"স্থিতি দণ্ড বিস্তাৰিত বা সংকুচিত কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permlab_fullScreenIntent" msgid="4310888199502509104">"কোনো লক কৰি ৰখা ডিভাইচত জাননী পূৰ্ণ স্ক্ৰীনৰ কাৰ্যকলাপ হিচাপে প্ৰদৰ্শন কৰক"</string>
     <string name="permdesc_fullScreenIntent" msgid="1100721419406643997">"এপ্‌টোক কোনো লক কৰি ৰখা ডিভাইচত জাননী পূৰ্ণ স্ক্ৰীনৰ কাৰ্যকলাপ হিচাপে প্ৰদৰ্শন কৰিবলৈ অনুমতি দিয়ে"</string>
     <string name="permlab_install_shortcut" msgid="7451554307502256221">"শ্বৰ্টকাট ইনষ্টল কৰিব পাৰে"</string>
-    <string name="permdesc_install_shortcut" msgid="4476328467240212503">"এটা এপ্লিকেশ্বনক ব্যৱহাৰকাৰীৰ হস্তক্ষেপৰ অবিহনে গৃহ স্ক্ৰীণ শ্বৰ্টকাট যোগ কৰিবলৈ অনুমতি দিয়ে।"</string>
+    <string name="permdesc_install_shortcut" msgid="4476328467240212503">"এটা এপ্লিকেশ্বনক ব্যৱহাৰকাৰীৰ হস্তক্ষেপৰ অবিহনে গৃহ স্ক্ৰীন শ্বৰ্টকাট যোগ কৰিবলৈ অনুমতি দিয়ে।"</string>
     <string name="permlab_uninstall_shortcut" msgid="295263654781900390">"শ্বৰ্টকাট আনইনষ্টল কৰিব পাৰে"</string>
-    <string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"ব্যৱহাৰকাৰীৰ হস্তক্ষেপৰ অবিহনে গৃহ স্ক্ৰীণৰ শ্বৰ্টকাটসমূহ আঁতৰাবলৈ এপ্লিকেশ্বনক অনুমতি দিয়ে।"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"ব্যৱহাৰকাৰীৰ হস্তক্ষেপৰ অবিহনে গৃহ স্ক্ৰীনৰ শ্বৰ্টকাটসমূহ আঁতৰাবলৈ এপ্লিকেশ্বনক অনুমতি দিয়ে।"</string>
     <string name="permlab_processOutgoingCalls" msgid="4075056020714266558">"বহিৰ্গামী কলসমূহ অন্য ক\'ৰবালৈ পঠিয়াওক"</string>
     <string name="permdesc_processOutgoingCalls" msgid="7833149750590606334">"এটা বৰ্হিগামী কল কৰি থকাৰ সময়ত ডায়েল কৰা নম্বৰ চাবলৈ আৰু লগতে এটা পৃথক নম্বৰলৈ কল সংযোগ কৰিবলৈ বা সকলোকে একেলগে বন্ধ কৰিবলৈ এপক অনুমতি দিয়ে।"</string>
     <string name="permlab_answerPhoneCalls" msgid="4131324833663725855">"ফ\'ন কলৰ উত্তৰ দিব পাৰে"</string>
-    <string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"এপটোক অন্তৰ্গামী ফ\'ন কলৰ উত্তৰ দিবলৈ অনুমতি দিয়ে।"</string>
+    <string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"এপ্‌টোক অন্তৰ্গামী ফ\'ন কলৰ উত্তৰ দিবলৈ অনুমতি দিয়ে।"</string>
     <string name="permlab_receiveSms" msgid="505961632050451881">"পাঠ বার্তা (এছএমএছ) বোৰ লাভ কৰক"</string>
-    <string name="permdesc_receiveSms" msgid="1797345626687832285">"এপটোক এছএমএছ বাৰ্তাবোৰ পাবলৈ আৰু প্ৰক্ৰিয়া সম্পন্ন কৰিবলৈ অনুমতি দিয়ে৷ ইয়াৰ অৰ্থ এইটোৱেই যে এপটোৱে আপোনাক বাৰ্তাবোৰ নেদেখুৱাকৈয়ে আপোনাৰ ডিভাইচলৈ পঠিওৱা বাৰ্তাবোৰ নিৰীক্ষণ কৰিব বা মচিব পাৰে৷"</string>
+    <string name="permdesc_receiveSms" msgid="1797345626687832285">"এপ্‌টোক এছএমএছ বাৰ্তাবোৰ পাবলৈ আৰু প্ৰক্ৰিয়া সম্পন্ন কৰিবলৈ অনুমতি দিয়ে৷ ইয়াৰ অৰ্থ এইটোৱেই যে এপটোৱে আপোনাক বাৰ্তাবোৰ নেদেখুৱাকৈয়ে আপোনাৰ ডিভাইচলৈ পঠিওৱা বাৰ্তাবোৰ নিৰীক্ষণ কৰিব বা মচিব পাৰে৷"</string>
     <string name="permlab_receiveMms" msgid="4000650116674380275">"পাঠ বার্তা (এমএমএছ) বোৰ লাভ কৰক"</string>
     <string name="permdesc_receiveMms" msgid="958102423732219710">"এমএমএছ বার্তাবোৰ লাভ আৰু ইয়াৰ প্ৰক্ৰিয়া সম্পন্ন কৰিবলৈ এপক অনুমতি দিয়ে। ইয়াৰ অৰ্থ হৈছে এই এপে আপোনাৰ ডিভাইচলৈ প্ৰেৰণ কৰা বার্তাসমূহ আপোনাক নেদেখুৱাকৈয়ে পৰ্যবেক্ষণ আৰু মচিব পাৰে।"</string>
     <string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"চেল সম্প্ৰচাৰ বাৰ্তাসমূহ ফৰৱাৰ্ড কৰক"</string>
@@ -368,26 +368,26 @@
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"চেল সম্প্ৰচাৰৰ বার্তাবোৰ পঢ়ক"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"আপোনাৰ ডিভাইচে লাভ কৰা চেল সম্প্ৰচাৰৰ বার্তাবোৰ পঢ়িবলৈ এপক অনুমতি দিয়ে। আপোনাক জৰুৰীকালীন পৰিস্থিতিবোৰত সর্তক কৰিবলৈ চেল সম্প্ৰচাৰৰ বার্তাবোৰ প্ৰেৰণ কৰা হয়। জৰুৰীকালীন চেল সম্প্ৰচাৰ লাভ কৰাৰ সময়ত আপোনাৰ ডিভাইচৰ কাৰ্যদক্ষতা বা কাৰ্যপ্ৰণালীত ক্ষতিকাৰক এপবোৰে হস্তক্ষেপ কৰিব পাৰে।"</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"আপুনি সদস্যভুক্ত হোৱা ফীডসমূহ পঢ়ক"</string>
-    <string name="permdesc_subscribedFeedsRead" msgid="6911349196661811865">"বৰ্তমান ছিংক কৰা ফীডৰ সবিশেষ লাভ কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+    <string name="permdesc_subscribedFeedsRead" msgid="6911349196661811865">"বৰ্তমান ছিংক কৰা ফীডৰ সবিশেষ লাভ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permlab_sendSms" msgid="7757368721742014252">"এছএমএছ ৰ বার্তাবোৰ প্ৰেৰণ কৰিব আৰু চাব পাৰে"</string>
-    <string name="permdesc_sendSms" msgid="6757089798435130769">"এপটোক এছএমএছ বাৰ্তা পঠিয়াবলৈ অনুমতি দিয়ে৷ ইয়াৰ ফলত অপ্ৰত্যাশিত মাচুল ভৰিবলগা হ\'ব পাৰে৷ ক্ষতিকাৰক এপসমূহে আপোনাৰ অনুমতি নোলোৱাকৈয়ে বাৰ্তা পঠিয়াই আপোনাৰ পৰা মাচুল কাটিব পাৰে৷"</string>
+    <string name="permdesc_sendSms" msgid="6757089798435130769">"এপ্‌টোক এছএমএছ বাৰ্তা পঠিয়াবলৈ অনুমতি দিয়ে৷ ইয়াৰ ফলত অপ্ৰত্যাশিত মাচুল ভৰিবলগা হ\'ব পাৰে৷ ক্ষতিকাৰক এপসমূহে আপোনাৰ অনুমতি নোলোৱাকৈয়ে বাৰ্তা পঠিয়াই আপোনাৰ পৰা মাচুল কাটিব পাৰে৷"</string>
     <string name="permlab_readSms" msgid="5164176626258800297">"আপোনাৰ পাঠ বার্তাবোৰ পঢ়ক (এছএমএছ বা এমএমএছ)"</string>
     <string name="permdesc_readSms" product="tablet" msgid="7912990447198112829">"এই এপ্‌টোৱে আপোনাৰ টেবলেটটোত সংৰক্ষিত আটাইবোৰ এছএমএছ (পাঠ) বাৰ্তা পঢ়িব পাৰে।"</string>
     <string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"এই এপ্‌টোৱে আপোনাৰ Android TV ডিভাইচত ষ্ট’ৰ কৰি ৰখা আটাইবোৰ এছএমএছ (পাঠ) বাৰ্তা পঢ়িব পাৰে।"</string>
     <string name="permdesc_readSms" product="default" msgid="774753371111699782">"এই এপ্‌টোৱে আপোনাৰ ফ\'নত সংৰক্ষিত আটাইবোৰ এছএমএছ (পাঠ) বাৰ্তা পঢ়িব পাৰে।"</string>
     <string name="permlab_receiveWapPush" msgid="4223747702856929056">"পাঠ বার্তা (WAP) বোৰ লাভ কৰক"</string>
-    <string name="permdesc_receiveWapPush" msgid="1638677888301778457">"এপটোক WAP বাৰ্তাবোৰ পাবলৈ আৰু প্ৰক্ৰিয়া সম্পন্ন কৰিবলৈ অনুমতি দিয়ে৷ এই অনুমতিত আপোনালৈ পঠিওৱা বাৰ্তাবোৰ আপোনাক নেদেখুৱাকৈয়ে নিৰীক্ষণ বা মচাৰ সক্ষমতা অন্তৰ্ভুক্ত থাকে৷"</string>
+    <string name="permdesc_receiveWapPush" msgid="1638677888301778457">"এপ্‌টোক WAP বাৰ্তাবোৰ পাবলৈ আৰু প্ৰক্ৰিয়া সম্পন্ন কৰিবলৈ অনুমতি দিয়ে৷ এই অনুমতিত আপোনালৈ পঠিওৱা বাৰ্তাবোৰ আপোনাক নেদেখুৱাকৈয়ে নিৰীক্ষণ বা মচাৰ সক্ষমতা অন্তৰ্ভুক্ত থাকে৷"</string>
     <string name="permlab_getTasks" msgid="7460048811831750262">"চলি থকা এপসমূহ বিচাৰি উলিয়াওক"</string>
-    <string name="permdesc_getTasks" msgid="7388138607018233726">"এপটোক বৰ্তমানে আৰু শেহতীয়াভাৱে চলি থকা কাৰ্যসমূহৰ বিষয়ে তথ্য পুনৰুদ্ধাৰ কৰিবলৈ অনুমতি দিয়ে৷ এইটোৱে এপটোক ডিভাইচটোত কোনবোৰ এপ্লিকেশ্বন ব্যৱহাৰ হৈ আছে তাৰ বিষয়ে তথ্য বিচাৰি উলিয়াবলৈ অনুমতি দিব পাৰে৷"</string>
+    <string name="permdesc_getTasks" msgid="7388138607018233726">"এপ্‌টোক বৰ্তমানে আৰু শেহতীয়াভাৱে চলি থকা কাৰ্যসমূহৰ বিষয়ে তথ্য পুনৰুদ্ধাৰ কৰিবলৈ অনুমতি দিয়ে৷ এইটোৱে এপ্‌টোক ডিভাইচটোত কোনবোৰ এপ্লিকেশ্বন ব্যৱহাৰ হৈ আছে তাৰ বিষয়ে তথ্য বিচাৰি উলিয়াবলৈ অনুমতি দিব পাৰে৷"</string>
     <string name="permlab_manageProfileAndDeviceOwners" msgid="639849495253987493">"প্ৰ\'ফাইল আৰু ডিভাইচৰ গৰাকীসকলক পৰিচালনা কৰিব পাৰে"</string>
-    <string name="permdesc_manageProfileAndDeviceOwners" msgid="7304240671781989283">"প্ৰ\'ফাইলৰ গৰাকী আৰু ডিভাইচৰ গৰাকী ছেট কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+    <string name="permdesc_manageProfileAndDeviceOwners" msgid="7304240671781989283">"প্ৰ\'ফাইলৰ গৰাকী আৰু ডিভাইচৰ গৰাকী ছেট কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permlab_reorderTasks" msgid="7598562301992923804">"চলি থকা এপসমূহক পুনৰাই ক্ৰমবদ্ধ কৰক"</string>
     <string name="permdesc_reorderTasks" msgid="8796089937352344183">"গতিবিধিক অগ্ৰভাগ আৰু নেপথ্যলৈ নিবলৈ এপক অনুমতি দিয়ে। এপে এই কার্য আপোনাৰ ইনপুট অবিহনেই কৰিব পাৰে।"</string>
     <string name="permlab_enableCarMode" msgid="893019409519325311">"গাড়ীৰ ম\'ড সক্ষম কৰক"</string>
-    <string name="permdesc_enableCarMode" msgid="56419168820473508">"গাড়ী ম\'ড সক্ষম কৰিবলৈ এপটোক অনুমতি দিয়ে৷"</string>
+    <string name="permdesc_enableCarMode" msgid="56419168820473508">"গাড়ী ম\'ড সক্ষম কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে৷"</string>
     <string name="permlab_killBackgroundProcesses" msgid="6559320515561928348">"অন্য এপবোৰ বন্ধ কৰক"</string>
-    <string name="permdesc_killBackgroundProcesses" msgid="2357013583055434685">"এপটোক অন্য এপসমূহৰ নেপথ্যৰ প্ৰক্ৰিয়াসমূহ শেষ কৰিবলৈ অনুমতি দিয়ে৷ এই কার্যৰ বাবে অন্য এপসমূহ চলাটো বন্ধ হ\'ব পাৰে৷"</string>
-    <string name="permlab_systemAlertWindow" msgid="5757218350944719065">"এই এপটো অইন এপৰ ওপৰত প্ৰদৰ্শিত হ\'ব পাৰে"</string>
+    <string name="permdesc_killBackgroundProcesses" msgid="2357013583055434685">"এপ্‌টোক অন্য এপসমূহৰ নেপথ্যৰ প্ৰক্ৰিয়াসমূহ শেষ কৰিবলৈ অনুমতি দিয়ে৷ এই কার্যৰ বাবে অন্য এপসমূহ চলাটো বন্ধ হ\'ব পাৰে৷"</string>
+    <string name="permlab_systemAlertWindow" msgid="5757218350944719065">"এই এপ্‌টো অইন এপৰ ওপৰত প্ৰদৰ্শিত হ\'ব পাৰে"</string>
     <string name="permdesc_systemAlertWindow" msgid="1145660714855738308">"এই এপ্‌টো অন্য এপৰ ওপৰত বা স্ক্ৰীনৰ অন্য অংশত প্ৰদৰ্শিত হ\'ব পাৰে। এই কাৰ্যই এপৰ স্বাভাৱিক ব্যৱহাৰত ব্যাঘাত জন্মাব পাৰে আৰু অন্য এপ্‌সমূহক স্ক্ৰীনত কেনেকৈ দেখা পোৱা যায় সেইটো সলনি কৰিব পাৰে।"</string>
     <string name="permlab_runInBackground" msgid="541863968571682785">"নেপথ্যত চলিব পাৰে"</string>
     <string name="permdesc_runInBackground" msgid="4344539472115495141">"এই এপ্‌টো নেপথ্যত চলিব পাৰে। ইয়াৰ ফলত বেটাৰী সোনকালে শেষ হ’ব পাৰে।"</string>
@@ -398,15 +398,15 @@
     <string name="permdesc_persistentActivity" product="tv" msgid="6800526387664131321">"এপ্‌টোক মেম’ৰীত নিজৰ বাবে প্ৰয়োজনীয় ঠাই পৃথক কৰিবলৈ অনুমতি দিয়ে। এই কার্যই আপোনাৰ Android TV ডিভাইচটোক লেহেমীয়া কৰি অন্য এপ্‌সমূহৰ বাবে উপলব্ধ মেম’ৰীক সীমাবদ্ধ কৰিব পাৰে।"</string>
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"মেম\'ৰিত নিজৰ বাবে প্ৰয়োজনীয় ঠাই পৃথক কৰিবলৈ এপক অনুমতি দিয়ে। এই কার্যই ফ\'নৰ কার্যক লেহেমীয়া কৰি অন্য এপবোৰৰ বাবে উপলব্ধ মেম\'ৰিক সীমাবদ্ধ কৰে।"</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"অগ্ৰভূমিৰ সেৱা চলাব পাৰে"</string>
-    <string name="permdesc_foregroundService" msgid="8720071450020922795">"এপটোক অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে।"</string>
+    <string name="permdesc_foregroundService" msgid="8720071450020922795">"এপ্‌টোক অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে।"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"এপৰ ষ্ট’ৰেজৰ খালী ঠাই হিচাপ কৰক"</string>
-    <string name="permdesc_getPackageSize" msgid="742743530909966782">"এপটোক ইয়াৰ ক\'ড, ডেটা আৰু কেশ্বৰ আকাৰ বিচাৰি উলিয়াবলৈ অনুমতি দিয়ে"</string>
+    <string name="permdesc_getPackageSize" msgid="742743530909966782">"এপ্‌টোক ইয়াৰ ক\'ড, ডেটা আৰু কেশ্বৰ আকাৰ বিচাৰি উলিয়াবলৈ অনুমতি দিয়ে"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"ছিষ্টেম ছেটিংহ সংশোধন কৰক"</string>
     <string name="permdesc_writeSettings" msgid="8293047411196067188">"এপ্‌টোক ছিষ্টেমৰ ছেটিঙৰ ডেটা সংশোধন কৰিবলৈ অনুমতি দিয়ে৷ ক্ষতিকাৰক এপ্‌সমূহে আপোনাৰ ছিষ্টেম কনফিগাৰেশ্বনক ক্ষতিগ্ৰস্ত কৰিব পাৰে৷"</string>
     <string name="permlab_receiveBootCompleted" msgid="6643339400247325379">"আৰম্ভ হোৱাৰ সময়ত চলাওক"</string>
-    <string name="permdesc_receiveBootCompleted" product="tablet" msgid="5565659082718177484">"ছিষ্টেমে বুট কৰা কাৰ্য সমাপ্ত কৰাৰ লগে লগে এপটোক নিজে নিজে আৰম্ভ হ\'বলৈ অনুমতি দিয়ে। ইয়াৰ ফলত ফ\'নটো ষ্টাৰ্ট হওতে বেছি সময়ৰ প্ৰয়োজন হ\'ব পাৰে, আৰু এপটো সদায় চলি থকাৰ কাৰণে ফ\'নটো সামগ্ৰিকভাৱে লেহেমীয়া হ\'ব পাৰে।"</string>
+    <string name="permdesc_receiveBootCompleted" product="tablet" msgid="5565659082718177484">"ছিষ্টেমে বুট কৰা কাৰ্য সমাপ্ত কৰাৰ লগে লগে এপ্‌টোক নিজে নিজে আৰম্ভ হ\'বলৈ অনুমতি দিয়ে। ইয়াৰ ফলত ফ\'নটো ষ্টাৰ্ট হওতে বেছি সময়ৰ প্ৰয়োজন হ\'ব পাৰে, আৰু এপ্‌টো সদায় চলি থকাৰ কাৰণে ফ\'নটো সামগ্ৰিকভাৱে লেহেমীয়া হ\'ব পাৰে।"</string>
     <string name="permdesc_receiveBootCompleted" product="tv" msgid="4900842256047614307">"ছিষ্টেমে বুটিং সমাপ্ত কৰাৰ লগে লগে এই এপ্‌টোক নিজে নিজে আৰম্ভ হ’বলৈ অনুমতি দিয়ে। এই কাৰ্যৰ বাবে আপোনাৰ Android TV ডিভাইচটো আৰম্ভ হ’বলৈ দীঘলীয়া সময়ৰ প্ৰয়োজন হ’ব পাৰে আৰু সকলো সময়তে চলি থাকি এপ্‌টোক সামগ্ৰিকভাৱে ডিভাইচটো লেহেমীয়া কৰিবলৈ দিয়ে।"</string>
-    <string name="permdesc_receiveBootCompleted" product="default" msgid="7912677044558690092">"ছিষ্টেমে বুট কৰা কাৰ্য সমাপ্ত কৰাৰ লগে লগে এপটোক নিজে নিজে আৰম্ভ হ\'বলৈ অনুমতি দিয়ে। ইয়াৰ ফলত ফ\'নটো ষ্টাৰ্ট হওতে বেছি সময়ৰ প্ৰয়োজন হ\'ব পাৰে, আৰু এপটো সদায় চলি থকাৰ কাৰণে ফ\'নটো সামগ্ৰিকভাৱে লেহেমীয়া হ\'ব পাৰে।"</string>
+    <string name="permdesc_receiveBootCompleted" product="default" msgid="7912677044558690092">"ছিষ্টেমে বুট কৰা কাৰ্য সমাপ্ত কৰাৰ লগে লগে এপ্‌টোক নিজে নিজে আৰম্ভ হ\'বলৈ অনুমতি দিয়ে। ইয়াৰ ফলত ফ\'নটো ষ্টাৰ্ট হওতে বেছি সময়ৰ প্ৰয়োজন হ\'ব পাৰে, আৰু এপ্‌টো সদায় চলি থকাৰ কাৰণে ফ\'নটো সামগ্ৰিকভাৱে লেহেমীয়া হ\'ব পাৰে।"</string>
     <string name="permlab_broadcastSticky" msgid="4552241916400572230">"ষ্টিকী ব্ৰ\'ডকাষ্ট পঠিয়াওক"</string>
     <string name="permdesc_broadcastSticky" product="tablet" msgid="5058486069846384013">"সম্প্ৰচাৰৰ শেষত বাকী ৰোৱা ষ্টিকী ব্ৰ\'ডকাষ্টবোৰ প্ৰেৰণ কৰিবলৈ এপক অনুমতি দিয়ে। ইয়াক অত্য়ধিক ব্যৱহাৰ কৰাৰ ফলত মেম\'ৰি অধিক খৰচ হোৱাৰ বাবে টেবলেট লেহেমীয়া বা অস্থিৰ হৈ পৰে।"</string>
     <string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"এপ্‌টোক ব্ৰ’ডকাষ্ট শেষ হোৱাৰ পাছত বাকী থকা ষ্টিকী ব্ৰ’ডকাষ্টবোৰ পঠিয়াবলৈ অনুমতি দিয়ে। ইয়াক অত্যধিক ব্যৱহাৰ কৰিলে আপোনাৰ Android TV ডিভাইচটোক অতি বেছি পৰিমাণৰ মেম’ৰী খৰচ কৰাই লেহেমীয়া অথবা অস্থিৰ কৰিব পাৰে।"</string>
@@ -464,62 +464,62 @@
     <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"কোনো এপ্লিকেশ্বন অথবা সেৱাক কেমেৰা ডিভাইচসমূহ খোলা অথবা বন্ধ কৰাৰ বিষয়ে কলবেকসমূহ গ্ৰহণ কৰিবলৈ অনুমতি দিয়ক।"</string>
     <string name="permdesc_cameraOpenCloseListener" msgid="2002636131008772908">"যিকোনো কেমেৰা ডিভাইচ খুলি থকা অথবা বন্ধ কৰি থকাৰ সময়ত (কোনো এপ্লিকেশ্বনৰ দ্বাৰা) এই এপ্‌টোৱে কলবেক গ্ৰহণ কৰিব পাৰে।"</string>
     <string name="permlab_vibrate" msgid="8596800035791962017">"কম্পন নিয়ন্ত্ৰণ কৰক"</string>
-    <string name="permdesc_vibrate" msgid="8733343234582083721">"ভাইব্ৰেটৰ নিয়ন্ত্ৰণ কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+    <string name="permdesc_vibrate" msgid="8733343234582083721">"ভাইব্ৰেটৰ নিয়ন্ত্ৰণ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permdesc_vibrator_state" msgid="7050024956594170724">"এপ্‌টোক কম্পন স্থিতিটো এক্সেছ কৰিবলৈ অনুমতি দিয়ে।"</string>
     <string name="permlab_callPhone" msgid="1798582257194643320">"পোনপটীয়াকৈ ফ\'ন নম্বৰলৈ কল কৰক"</string>
-    <string name="permdesc_callPhone" msgid="5439809516131609109">"আপোনাৰ কোনো ব্যাঘাত নোহোৱাকৈ ফ\'ন নম্বৰবোৰত কল কৰিবলৈ এপক অনুমতি দিয়ে৷ ইয়াৰ ফলত অপ্ৰত্যাশিত মাচুল ভৰিবলগা বা কলবোৰ কৰা হ\'ব পাৰে৷ মনত ৰাখিব যে ই এপটোক জৰুৰীকালীন নম্বৰবোৰত কল কৰিবলৈ অনুমতি নিদিয়ে৷ ক্ষতিকাৰক এপসমূহে আপোনাৰ অনুমতি নোলোৱাকৈয়ে কল কৰি আপোনাক টকা খৰছ কৰাব পাৰে৷"</string>
+    <string name="permdesc_callPhone" msgid="5439809516131609109">"আপোনাৰ কোনো ব্যাঘাত নোহোৱাকৈ ফ\'ন নম্বৰবোৰত কল কৰিবলৈ এপক অনুমতি দিয়ে৷ ইয়াৰ ফলত অপ্ৰত্যাশিত মাচুল ভৰিবলগা বা কলবোৰ কৰা হ\'ব পাৰে৷ মনত ৰাখিব যে ই এপ্‌টোক জৰুৰীকালীন নম্বৰবোৰত কল কৰিবলৈ অনুমতি নিদিয়ে৷ ক্ষতিকাৰক এপসমূহে আপোনাৰ অনুমতি নোলোৱাকৈয়ে কল কৰি আপোনাক টকা খৰছ কৰাব পাৰে৷"</string>
     <string name="permlab_accessImsCallService" msgid="442192920714863782">"আইএমএছ কল সেৱা ব্যৱহাৰ কৰিব পাৰে"</string>
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"আপোনাৰ হস্তক্ষেপৰ অবিহনে আইএমএছ সেৱা ব্যৱহাৰ কৰি কল কৰিবলৈ এপক অনুমতি দিয়ে।"</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"ফ\'নৰ স্থিতি আৰু পৰিচয় পঢ়ক"</string>
-    <string name="permdesc_readPhoneState" msgid="7229063553502788058">"ডিভাইচত থকা ফ\'নৰ সুবিধাসমূহ ব্যৱহাৰ কৰিবলৈ এপটোক অনুমতি দিয়ে৷ এই অনুমতিয়ে কোনো কল সক্ৰিয় হৈ থাককেই বা নাথাকক আৰু দূৰবৰ্তী নম্বৰটো কলৰ দ্বাৰা সংযোজিত হওকেই বা নহওক এপটোক ফ\'ন নম্বৰ আৰু ডিভাইচৰ পৰিচয় নিৰ্ধাৰণ কৰিবলৈ অনুমতি দিয়ে৷"</string>
+    <string name="permdesc_readPhoneState" msgid="7229063553502788058">"ডিভাইচত থকা ফ\'নৰ সুবিধাসমূহ ব্যৱহাৰ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে৷ এই অনুমতিয়ে কোনো কল সক্ৰিয় হৈ থাককেই বা নাথাকক আৰু দূৰবৰ্তী নম্বৰটো কলৰ দ্বাৰা সংযোজিত হওকেই বা নহওক এপ্‌টোক ফ\'ন নম্বৰ আৰু ডিভাইচৰ পৰিচয় নিৰ্ধাৰণ কৰিবলৈ অনুমতি দিয়ে৷"</string>
     <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"প্ৰাথমিক টেলিফ\'নী স্থিতি আৰু পৰিচয় পঢ়ক"</string>
     <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"এপ্‌টোক ডিভাইচটোৰ প্ৰাথমিক টেলিফ’নী সুবিধাসমূহ এক্সেছ কৰাৰ অনুমতি দিয়ে।"</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"ছিষ্টেমৰ জৰিয়তে কল কৰিব পাৰে"</string>
-    <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"কল কৰাৰ অভিজ্ঞতাক উন্নত কৰিবলৈ এপটোক ছিষ্টেমৰ জৰিয়তে কলসমূহ কৰিবলৈ দিয়ে।"</string>
+    <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"কল কৰাৰ অভিজ্ঞতাক উন্নত কৰিবলৈ এপ্‌টোক ছিষ্টেমৰ জৰিয়তে কলসমূহ কৰিবলৈ দিয়ে।"</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"ছিষ্টেমৰ জৰিয়তে কলবোৰ চোৱা আৰু নিয়ন্ত্ৰণ কৰা।"</string>
-    <string name="permdesc_callCompanionApp" msgid="8474168926184156261">"এপটোক ডিভাইচত চলি থকা কল চাবলৈ আৰু নিয়ন্ত্ৰণ কৰিবলৈ অনুমতি দিয়ে। কলৰ সংখ্যা আৰু কলবোৰৰ স্থিতি ইয়াত অন্তৰ্ভুক্ত হয়।"</string>
+    <string name="permdesc_callCompanionApp" msgid="8474168926184156261">"এপ্‌টোক ডিভাইচত চলি থকা কল চাবলৈ আৰু নিয়ন্ত্ৰণ কৰিবলৈ অনুমতি দিয়ে। কলৰ সংখ্যা আৰু কলবোৰৰ স্থিতি ইয়াত অন্তৰ্ভুক্ত হয়।"</string>
     <string name="permlab_exemptFromAudioRecordRestrictions" msgid="1164725468350759486">"অডিঅ’ ৰেকৰ্ড কৰাৰ প্ৰতিবন্ধকতাসমূহৰ পৰা ৰেহাই দিয়ক"</string>
     <string name="permdesc_exemptFromAudioRecordRestrictions" msgid="2425117015896871976">"অডিঅ’ ৰেকৰ্ড কৰাৰ প্ৰতিবন্ধকতাসমূহৰ পৰা এপ্‌টোক ৰেহাই দিয়ক।"</string>
     <string name="permlab_acceptHandover" msgid="2925523073573116523">"অইন এটা এপত আৰম্ভ হোৱা কল এটা অব্যাহত ৰাখিব পাৰে"</string>
-    <string name="permdesc_acceptHandovers" msgid="7129026180128626870">"এপটোক এনে কল কৰিবলৈ দিয়ে যিটোৰ আৰম্ভণি অইন এটা এপত হৈছিল।"</string>
+    <string name="permdesc_acceptHandovers" msgid="7129026180128626870">"এপ্‌টোক এনে কল কৰিবলৈ দিয়ে যিটোৰ আৰম্ভণি অইন এটা এপত হৈছিল।"</string>
     <string name="permlab_readPhoneNumbers" msgid="5668704794723365628">"ফ\'ন নম্বৰসমূহ পঢ়ে"</string>
-    <string name="permdesc_readPhoneNumbers" msgid="7368652482818338871">"এপটোক ডিভাইচটোৰ ফ\'ন নম্বৰসমূহ চাবলৈ অনুমতি দিয়ে।"</string>
+    <string name="permdesc_readPhoneNumbers" msgid="7368652482818338871">"এপ্‌টোক ডিভাইচটোৰ ফ\'ন নম্বৰসমূহ চাবলৈ অনুমতি দিয়ে।"</string>
     <string name="permlab_wakeLock" product="automotive" msgid="1904736682319375676">"গাড়ীৰ স্ক্রীনখন অন কৰি ৰখা"</string>
     <string name="permlab_wakeLock" product="tablet" msgid="1527660973931694000">"টে\'বলেট সুপ্ত অৱস্থালৈ যোৱাত বাধা দিয়ক"</string>
     <string name="permlab_wakeLock" product="tv" msgid="2856941418123343518">"আপোনাৰ Android TV ডিভাইচটো সুপ্ত অৱস্থালৈ যোৱাত বাধা দিয়ক"</string>
     <string name="permlab_wakeLock" product="default" msgid="569409726861695115">"ফ\'ন সুপ্ত অৱস্থালৈ যোৱাত বাধা দিয়ক"</string>
     <string name="permdesc_wakeLock" product="automotive" msgid="5995045369683254571">"এপ্‌টোক গাড়ীৰ স্ক্রীনখন অন কৰি ৰাখিবলৈ অনুমতি দিয়ে।"</string>
-    <string name="permdesc_wakeLock" product="tablet" msgid="2441742939101526277">"টে\'বলেট সুপ্ত অৱস্থালৈ যোৱাৰ পৰা প্ৰতিৰোধ কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+    <string name="permdesc_wakeLock" product="tablet" msgid="2441742939101526277">"টে\'বলেট সুপ্ত অৱস্থালৈ যোৱাৰ পৰা প্ৰতিৰোধ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permdesc_wakeLock" product="tv" msgid="2329298966735118796">"এপ্‌টোক আপোনাৰ Android TV ডিভাইচটো সুপ্ত অৱস্থালৈ যোৱাত বাধা দিবলৈ অনুমতি দিয়ে।"</string>
-    <string name="permdesc_wakeLock" product="default" msgid="3689523792074007163">"ফ\'ন সুপ্ত অৱস্থালৈ যোৱাৰ পৰা প্ৰতিৰোধ কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+    <string name="permdesc_wakeLock" product="default" msgid="3689523792074007163">"ফ\'ন সুপ্ত অৱস্থালৈ যোৱাৰ পৰা প্ৰতিৰোধ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permlab_transmitIr" msgid="8077196086358004010">"ইনফ্ৰাৰেড ট্ৰান্সমিট কৰিব পাৰে"</string>
     <string name="permdesc_transmitIr" product="tablet" msgid="5884738958581810253">"টে\'বলেটৰ ইনফ্ৰাৰেড ট্ৰান্সমিটাৰ ব্যৱহাৰ কৰিবলৈ এপক অনুমতি দিয়ে।"</string>
     <string name="permdesc_transmitIr" product="tv" msgid="3278506969529173281">"এপ্‌টোক আপোনাৰ Android TV ডিভাইচৰ ইনফ্ৰাৰেড ট্ৰান্সমিটাৰ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে।"</string>
     <string name="permdesc_transmitIr" product="default" msgid="8484193849295581808">"ফ\'নৰ ইনফ্ৰাৰেড ট্ৰান্সমিটাৰ ব্যৱহাৰ কৰিবলৈ এপক অনুমতি দিয়ে।"</string>
     <string name="permlab_setWallpaper" msgid="6959514622698794511">"ৱালপেপাৰ ছেট কৰক"</string>
-    <string name="permdesc_setWallpaper" msgid="2973996714129021397">"ছিষ্টেমৰ ৱালপেপাৰ ছেট কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+    <string name="permdesc_setWallpaper" msgid="2973996714129021397">"ছিষ্টেমৰ ৱালপেপাৰ ছেট কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permlab_setWallpaperHints" msgid="1153485176642032714">"আপোনাৰ ৱালপেপাৰৰ আকাৰ মিলাওক"</string>
-    <string name="permdesc_setWallpaperHints" msgid="6257053376990044668">"ছিষ্টেমৰ ৱালপেপাৰৰ আকাৰ হিণ্ট ছেট কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+    <string name="permdesc_setWallpaperHints" msgid="6257053376990044668">"ছিষ্টেমৰ ৱালপেপাৰৰ আকাৰ হিণ্ট ছেট কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permlab_setTimeZone" msgid="7922618798611542432">"সময় মণ্ডল ছেট কৰক"</string>
-    <string name="permdesc_setTimeZone" product="tablet" msgid="1788868809638682503">"টে\'বলেটৰ সময় মণ্ডল সলনি কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+    <string name="permdesc_setTimeZone" product="tablet" msgid="1788868809638682503">"টে\'বলেটৰ সময় মণ্ডল সলনি কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permdesc_setTimeZone" product="tv" msgid="9069045914174455938">"এপ্‌টোক আপোনাৰ Android TV ডিভাইচটোৰ সময় মণ্ডল সলনি কৰিবলৈ অনুমতি দিয়ে।"</string>
-    <string name="permdesc_setTimeZone" product="default" msgid="4611828585759488256">"ফ\'নৰ সময় মণ্ডল সলনি কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+    <string name="permdesc_setTimeZone" product="default" msgid="4611828585759488256">"ফ\'নৰ সময় মণ্ডল সলনি কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permlab_getAccounts" msgid="5304317160463582791">"ডিভাইচত একাউণ্টবোৰ বিচাৰক"</string>
-    <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"এপটোক টেবলেটটোৰ জ্ঞাত একাউণ্টসমূহৰ সূচীখন পাবলৈ অনুমতি দিয়ে৷ এইটোৱে আপুনি ইনষ্টল কৰি ৰখা এপ্লিকেশ্বনসমূহে সৃষ্টি কৰা যিকোনো একাউণ্টক অন্তৰ্ভুক্ত কৰিব পাৰে৷"</string>
+    <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"এপ্‌টোক টেবলেটটোৰ জ্ঞাত একাউণ্টসমূহৰ সূচীখন পাবলৈ অনুমতি দিয়ে৷ এইটোৱে আপুনি ইনষ্টল কৰি ৰখা এপ্লিকেশ্বনসমূহে সৃষ্টি কৰা যিকোনো একাউণ্টক অন্তৰ্ভুক্ত কৰিব পাৰে৷"</string>
     <string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"আপোনাৰ Android TV ডিভাইচটোৰ পৰিচিত একাউণ্টসমূহৰ সূচীখন পাবলৈ অনুমতি দিয়ে। আপুনি ইনষ্টল কৰি ৰখা এপ্লিকেশ্বনসমূহে সৃষ্টি কৰা যিকোনো একাউণ্ট অন্তৰ্ভুক্ত হ’ব পাৰে।"</string>
-    <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"এপটোক ফ\'নটোৰ জ্ঞাত একাউণ্টসমূহৰ সূচীখন পাবলৈ অনুমতি দিয়ে৷ এইটোৱে আপুনি ইনষ্টল কৰি ৰখা এপ্লিকেশ্বনসমূহে সৃষ্টি কৰা যিকোনো একাউণ্টক অন্তৰ্ভুক্ত কৰিব পাৰে৷"</string>
+    <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"এপ্‌টোক ফ\'নটোৰ জ্ঞাত একাউণ্টসমূহৰ সূচীখন পাবলৈ অনুমতি দিয়ে৷ এইটোৱে আপুনি ইনষ্টল কৰি ৰখা এপ্লিকেশ্বনসমূহে সৃষ্টি কৰা যিকোনো একাউণ্টক অন্তৰ্ভুক্ত কৰিব পাৰে৷"</string>
     <string name="permlab_accessNetworkState" msgid="2349126720783633918">"নেটৱৰ্কৰ সংযোগবোৰ চাওক"</string>
-    <string name="permdesc_accessNetworkState" msgid="4394564702881662849">"মজুত থকা আৰু সংযোগ হৈ থকা নেটৱৰ্ক সংযোগসমূহৰ বিষয়ে তথ্য চাবলৈ এপটোক অনুমতি দিয়ে৷"</string>
+    <string name="permdesc_accessNetworkState" msgid="4394564702881662849">"মজুত থকা আৰু সংযোগ হৈ থকা নেটৱৰ্ক সংযোগসমূহৰ বিষয়ে তথ্য চাবলৈ এপ্‌টোক অনুমতি দিয়ে৷"</string>
     <string name="permlab_createNetworkSockets" msgid="3224420491603590541">"সম্পূর্ণ নেটৱর্কৰ সুবিধা লাভ কৰিব পাৰে"</string>
-    <string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"এপটোক নেটৱৰ্ক ছ\'কেটবোৰ সৃষ্টি কৰিবলৈ আৰু কাষ্টম নেটৱৰ্ক প্ৰ\'ট\'কল ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে৷ ব্ৰাউজাৰ আৰু অন্য এপ্লিকেশ্বনসমূহে ইণ্টাৰনেটলৈ ডেটা পঠিওৱা মাধ্য়ম প্ৰদান কৰে, গতিকে ইণ্টাৰনেটলৈ ডেটা পঠিয়াবলৈ এই অনুমতিৰ প্ৰয়োজন নাই৷"</string>
+    <string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"এপ্‌টোক নেটৱৰ্ক ছ\'কেটবোৰ সৃষ্টি কৰিবলৈ আৰু কাষ্টম নেটৱৰ্ক প্ৰ\'ট\'কল ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে৷ ব্ৰাউজাৰ আৰু অন্য এপ্লিকেশ্বনসমূহে ইণ্টাৰনেটলৈ ডেটা পঠিওৱা মাধ্য়ম প্ৰদান কৰে, গতিকে ইণ্টাৰনেটলৈ ডেটা পঠিয়াবলৈ এই অনুমতিৰ প্ৰয়োজন নাই৷"</string>
     <string name="permlab_changeNetworkState" msgid="8945711637530425586">"নেটৱৰ্কৰ সংযোগ সলনি কৰক"</string>
-    <string name="permdesc_changeNetworkState" msgid="649341947816898736">"নেটৱৰ্ক সংযোগৰ অৱস্থাটো সলনি কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+    <string name="permdesc_changeNetworkState" msgid="649341947816898736">"নেটৱৰ্ক সংযোগৰ অৱস্থাটো সলনি কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permlab_changeTetherState" msgid="9079611809931863861">"টেডাৰিং সংযোগ সলনি কৰক"</string>
-    <string name="permdesc_changeTetherState" msgid="3025129606422533085">"টেডাৰ হৈ থকা ইণ্টাৰনেট সংযোগৰ অৱস্থা সলনি কৰিবলৈ এপটোক অনুমতি দিয়ে৷"</string>
+    <string name="permdesc_changeTetherState" msgid="3025129606422533085">"টেডাৰ হৈ থকা ইণ্টাৰনেট সংযোগৰ অৱস্থা সলনি কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে৷"</string>
     <string name="permlab_accessWifiState" msgid="5552488500317911052">"ৱাই-ফাইৰ সংযোগবোৰ চাওক"</string>
     <string name="permdesc_accessWifiState" msgid="6913641669259483363">"ৱাই-ফাই সক্ষম কৰা হ’ল নে নাই আৰু সংযোগ হৈ থকা ৱাই-ফাই ডিভাইচসমূহৰ নামবোৰৰ দৰে ৱাই-ফাইৰ ইণ্টাৰনেট সম্পর্কীয় তথ্য চাবলৈ এপক অনুমতি দিয়ে।"</string>
     <string name="permlab_changeWifiState" msgid="7947824109713181554">"ৱাই-ফাই সংযোগ কৰক আৰু ইয়াৰ সংযোগ বিচ্ছিন্ন কৰক"</string>
-    <string name="permdesc_changeWifiState" msgid="7170350070554505384">"এপটোক ৱাই-ফাই এক্সেছ পইণ্টলৈ সংযোগ কৰিবলৈ আৰু তাৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ আৰু ৱাই-ফাই নেটৱৰ্কসমূহৰ বাবে ডিভাইচ কনফিগাৰেশ্বনত সাল-সলনি কৰিবলৈ অনুমতি দিয়ে৷"</string>
+    <string name="permdesc_changeWifiState" msgid="7170350070554505384">"এপ্‌টোক ৱাই-ফাই এক্সেছ পইণ্টলৈ সংযোগ কৰিবলৈ আৰু তাৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ আৰু ৱাই-ফাই নেটৱৰ্কসমূহৰ বাবে ডিভাইচ কনফিগাৰেশ্বনত সাল-সলনি কৰিবলৈ অনুমতি দিয়ে৷"</string>
     <string name="permlab_changeWifiMulticastState" msgid="285626875870754696">"ৱাই-ফাই মাল্টিকাষ্ট প্ৰচাৰৰ অনুমতি দিয়ক"</string>
     <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"কেৱল আপোনাৰ টেবলেটটোৱেই নহয়, মাল্টিকাষ্ট ঠিকনা ব্যৱহাৰ কৰি এটা ৱাই-ফাই নেটৱর্কত থকা আটাইবোৰ ডিভাইচলৈ পঠিওৱা পেকেট লাভ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে। এই কার্যই নন মাল্টিকাষ্ট ম\'ডতকৈ অধিক বেটাৰী ব্যৱহাৰ কৰে।"</string>
     <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"কেৱল আপোনাৰ Android TV ডিভাইচটোৱেই নহয়, মাল্টিকাষ্ট ঠিকনাবোৰ ব্যৱহাৰ কৰি এটা ৱাই-ফাই নেটৱর্কত থকা আটাইবোৰ ডিভাইচলৈ পঠিওৱা পেকেট লাভ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে। এই কার্যই নন-মাল্টিকাষ্ট ম’ডতকৈ অধিক পাৱাৰ ব্যৱহাৰ কৰে।"</string>
@@ -529,15 +529,15 @@
     <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"এপ্‌টোক আপোনাৰ Android TV ডিভাইচটোত ব্লুটুথ কনফিগাৰ কৰিবলৈ আৰু ৰিম’ট ডিভাইচসমূহ বিচাৰি উলিয়াবলৈ আৰু পেয়াৰ কৰিবলৈ অনুমতি দিয়ে।"</string>
     <string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"স্থানীয় ব্লুটুথ ফ’ন কনফিগাৰ কৰিবলৈ আৰু দূৰৱৰ্তী ডিভাইচসমূহৰ সৈতে পেয়াৰ কৰিবলৈ আৰু বিচাৰি উলিয়াবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permlab_accessWimaxState" msgid="7029563339012437434">"WiMAXৰ লগত সংযোগ কৰক আৰু ইয়াৰ পৰা সংযোগ বিচ্ছিন্ন কৰক"</string>
-    <string name="permdesc_accessWimaxState" msgid="5372734776802067708">"WiMAX সক্ষম হৈ আছেনে নাই আৰু সংযোজিত যিকোনো WiMAX নেটৱৰ্কৰ বিষয়ে তথ্য নিৰ্ধাৰণ কৰিবলৈ এপটোক অনুমতি দিয়ে৷"</string>
+    <string name="permdesc_accessWimaxState" msgid="5372734776802067708">"WiMAX সক্ষম হৈ আছেনে নাই আৰু সংযোজিত যিকোনো WiMAX নেটৱৰ্কৰ বিষয়ে তথ্য নিৰ্ধাৰণ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে৷"</string>
     <string name="permlab_changeWimaxState" msgid="6223305780806267462">"WiMAXৰ স্থিতি সলনি কৰক"</string>
-    <string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"এপটোক টেবলেটলৈ সংযোগ কৰিবলৈ আৰু WiMAX নেটৱৰ্কসমূহৰ পৰা টেবলেটৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ অনুমতি দিয়ে৷"</string>
+    <string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"এপ্‌টোক টেবলেটলৈ সংযোগ কৰিবলৈ আৰু WiMAX নেটৱৰ্কসমূহৰ পৰা টেবলেটৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ অনুমতি দিয়ে৷"</string>
     <string name="permdesc_changeWimaxState" product="tv" msgid="5373274458799425276">"এপ্‌টোক আপোনাৰ Android TV ডিভাইচৰ সৈতে সংযোগ কৰিবলৈ আৰু WiMAX নেটৱৰ্কসমূহৰ পৰা আপোনাৰ Android TV ডিভাইচৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ অনুমতি দিয়ে।"</string>
-    <string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"এপটোক ফ\'নলৈ সংযোগ কৰিবলৈ আৰু WiMAX নেটৱৰ্কসমূহৰ পৰা ফ\'নৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ অনুমতি দিয়ে৷"</string>
+    <string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"এপ্‌টোক ফ\'নলৈ সংযোগ কৰিবলৈ আৰু WiMAX নেটৱৰ্কসমূহৰ পৰা ফ\'নৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ অনুমতি দিয়ে৷"</string>
     <string name="permlab_bluetooth" msgid="586333280736937209">"ব্লুটুথ ডিভাইচবোৰৰ সৈতে পেয়াৰ কৰক"</string>
-    <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"টেবলেটত ব্লুটুথৰ কনফিগাৰেশ্বন চাবলৈ আৰু যোৰা লগোৱা ডিভাইচসমূহৰ জৰিয়তে সংযোগ কৰিবলৈ আৰু সংযোগৰ অনুৰোধ স্বীকাৰ কৰিবলৈ এপটোক অনুমতি দিয়ে৷"</string>
+    <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"টেবলেটত ব্লুটুথৰ কনফিগাৰেশ্বন চাবলৈ আৰু যোৰা লগোৱা ডিভাইচসমূহৰ জৰিয়তে সংযোগ কৰিবলৈ আৰু সংযোগৰ অনুৰোধ স্বীকাৰ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে৷"</string>
     <string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"এপ্‌টোক আপোনাৰ Android TV ডিভাইচটোত ব্লুটুথৰ কনফিগাৰেশ্বন চাবলৈ আৰু পেয়াৰ কৰি থোৱা ডিভাইচসমূহৰ সৈতে সংযোগ কৰিবলৈ আৰু গ্ৰহণ কৰিবলৈ অনুমতি দিয়ে।"</string>
-    <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"ফ\'নটোত ব্লুটুথৰ কনফিগাৰেশ্বন চাবলৈ আৰু যোৰা লগোৱা ডিভাইচসমূহৰ জৰিয়তে সংযোগ কৰিবলৈ আৰু সংযোগৰ অনুৰোধ স্বীকাৰ কৰিবলৈ এপটোক অনুমতি দিয়ে৷"</string>
+    <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"ফ\'নটোত ব্লুটুথৰ কনফিগাৰেশ্বন চাবলৈ আৰু যোৰা লগোৱা ডিভাইচসমূহৰ জৰিয়তে সংযোগ কৰিবলৈ আৰু সংযোগৰ অনুৰোধ স্বীকাৰ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে৷"</string>
     <string name="permlab_bluetooth_scan" msgid="5402587142833124594">"নিকটৱৰ্তী ব্লুটুথ ডিভাইচ বিচাৰক আৰু তাৰ সৈতে পেয়াৰ কৰক"</string>
     <string name="permdesc_bluetooth_scan" product="default" msgid="6540723536925289276">"এপ্‌টোক নিকটৱৰ্তী ব্লুটুথ ডিভাইচ বিচাৰি উলিয়াবলৈ আৰু সেইসমূহৰ সৈতে পেয়াৰ কৰিবলৈ অনুমতি দিয়ে"</string>
     <string name="permlab_bluetooth_connect" msgid="6657463246355003528">"পেয়াৰ কৰা ব্লুটুথ ডিভাইচৰ সৈতে সংযোগ কৰক"</string>
@@ -551,9 +551,9 @@
     <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"অগ্ৰাধিকাৰ দিয়া NFC পৰিশোধ সেৱাৰ তথ্য"</string>
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"এপ্‌টোক অগ্ৰাধিকাৰ দিয়া nfc পৰিশোধ সেৱাৰ পঞ্জীকৃত সহায়কসমূহ আৰু পৰিশোধ কৰিব লগা লক্ষ্যস্থান দৰে তথ্য পাবলৈ অনুমতি দিয়ে।"</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"নিয়েৰ ফিল্ড কমিউনিকেশ্বন নিয়ন্ত্ৰণ কৰক"</string>
-    <string name="permdesc_nfc" msgid="8352737680695296741">"এপটোক নিয়েৰ ফিল্ড কমিউনিকেশ্বন (NFC) টেগ, কাৰ্ড আৰু ৰিডাৰসমূহৰ সৈতে যোগাযোগ কৰিবলৈ অনুমতি দিয়ে।"</string>
+    <string name="permdesc_nfc" msgid="8352737680695296741">"এপ্‌টোক নিয়েৰ ফিল্ড কমিউনিকেশ্বন (NFC) টেগ, কাৰ্ড আৰু ৰিডাৰসমূহৰ সৈতে যোগাযোগ কৰিবলৈ অনুমতি দিয়ে।"</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"আপোনাৰ স্ক্ৰীন লক অক্ষম কৰক"</string>
-    <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"এপটোক কী ল\'ক আৰু জড়িত হোৱা যিকোনো পাছৱৰ্ডৰ সুৰক্ষা অক্ষম কৰিব দিয়ে৷ উদাহৰণস্বৰূপে, কোনো অন্তৰ্গামী ফ\'ন কল উঠোৱাৰ সময়ত ফ\'নটোৱে কী-লকটো অক্ষম কৰে, তাৰ পিছত কল শেষ হ\'লেই কী লকটো পুনৰ সক্ষম কৰে৷"</string>
+    <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"এপ্‌টোক কী ল\'ক আৰু জড়িত হোৱা যিকোনো পাছৱৰ্ডৰ সুৰক্ষা অক্ষম কৰিব দিয়ে৷ উদাহৰণস্বৰূপে, কোনো অন্তৰ্গামী ফ\'ন কল উঠোৱাৰ সময়ত ফ\'নটোৱে কী-লকটো অক্ষম কৰে, তাৰ পাছত কল শেষ হ\'লেই কী লকটো পুনৰ সক্ষম কৰে৷"</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"স্ক্ৰীন লকৰ জটিলতাৰ অনুৰোধ"</string>
     <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"এপ্‌টোক স্ক্ৰীন লকৰ জটিলতাৰ স্তৰ (উচ্চ, মধ্যম, নিম্ন বা একেবাৰে নাই)ৰ বিষয়ে জানিবলৈ অনুমতি দিয়ে, যিয়ে স্ক্ৰীন লকৰ সম্ভাব্য দৈৰ্ঘ্য বা স্ক্ৰীন লকৰ প্ৰকাৰ দৰ্শায়। লগতে এপ্‌টোৱে ব্যৱহাৰকাৰীক স্ক্ৰীন লকটো এটা নিৰ্দিষ্ট স্তৰলৈ আপডে’ট কৰিবলৈ পৰামৰ্শ দিব পাৰে যিটো ব্যৱহাৰকাৰীয়ে অৱজ্ঞা কৰি পৰৱর্তী পৃষ্ঠালৈ যাব পাৰে। মনত ৰাখিব যে স্ক্ৰীন লকটো সাধাৰণ পাঠ হিচাপে ষ্ট\'ৰ কৰা নহয়; সেয়েহে, এপ্‌টোৱে সঠিক পাছৱৰ্ডটো জানিব নোৱাৰে।"</string>
     <string name="permlab_postNotification" msgid="4875401198597803658">"জাননী দেখুৱাওক"</string>
@@ -561,9 +561,9 @@
     <string name="permlab_useBiometric" msgid="6314741124749633786">"বায়োমেট্ৰিক হাৰ্ডৱেৰ ব্যৱহাৰ কৰক"</string>
     <string name="permdesc_useBiometric" msgid="7502858732677143410">"বিশ্বাসযোগ্য়তা প্ৰমাণীকৰণৰ বাবে এপক বায়োমেট্ৰিক হাৰ্ডৱেৰ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string>
     <string name="permlab_manageFingerprint" msgid="7432667156322821178">"ফিংগাৰপ্ৰিণ্ট হাৰ্ডৱেৰ পৰিচালনা কৰিব পাৰে"</string>
-    <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"ফিংগাৰপ্ৰিণ্ট টেম্প্লেটসমূহ যোগ কৰা বা মচাৰ পদ্ধতিসমূহ কামত লগাবলৈ নিৰ্দেশ দিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+    <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"ফিংগাৰপ্ৰিণ্ট টেম্প্লেটসমূহ যোগ কৰা বা মচাৰ পদ্ধতিসমূহ কামত লগাবলৈ নিৰ্দেশ দিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permlab_useFingerprint" msgid="1001421069766751922">"ফিংগাৰপ্ৰিণ্ট হাৰ্ডৱেৰ ব্যৱহাৰ কৰিব পাৰে"</string>
-    <string name="permdesc_useFingerprint" msgid="412463055059323742">"প্ৰমাণীকৰণৰ বাবে ফিংগাৰপ্ৰিণ্ট হাৰ্ডৱেৰ ব্যৱহাৰ কৰিবলৈ এপটোক অনুমতি দিয়ে"</string>
+    <string name="permdesc_useFingerprint" msgid="412463055059323742">"প্ৰমাণীকৰণৰ বাবে ফিংগাৰপ্ৰিণ্ট হাৰ্ডৱেৰ ব্যৱহাৰ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে"</string>
     <string name="permlab_audioWrite" msgid="8501705294265669405">"আপোনাৰ সংগীত সংগ্ৰহ সালসলনি কৰিবলৈ"</string>
     <string name="permdesc_audioWrite" msgid="8057399517013412431">"এপক আপোনাৰ সংগীত সংগ্ৰহ সালসলনি কৰিবলৈ দিয়ে।"</string>
     <string name="permlab_videoWrite" msgid="5940738769586451318">"আপোনাৰ ভিডিঅ’ সংগ্ৰহ সালসলনি কৰিবলৈ"</string>
@@ -689,7 +689,7 @@
     <string name="permlab_readSyncSettings" msgid="6250532864893156277">"ছিংকৰ ছেটিং পঢ়ক"</string>
     <string name="permdesc_readSyncSettings" msgid="1325658466358779298">"একাউণ্টৰ ছিংক ছেটিংবোৰ পঢ়িবলৈ এপক অনুমতি দিয়ে। যেনে, People এপ্‌টো কোনো একাউণ্টৰ সৈতে ছিংক কৰা হৈছে নে নাই সেয়া নির্ধাৰণ কৰিব পাৰে।"</string>
     <string name="permlab_writeSyncSettings" msgid="6583154300780427399">"ছিংকক অন আৰু অফ ট\'গল কৰক"</string>
-    <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"এপটোক কোনো একাউণ্টৰ ছিংক সম্পৰ্কীয় ছেটিংসমূহ সংশোধন কৰিবলৈ অনুমতি দিয়ে৷ উদাহৰণস্বৰূপে, এই কাৰ্যক কোনো একাউণ্টৰ জৰিয়তে People এপটোৰ ছিংক সক্ষম কৰিবলৈ ব্যৱহাৰ কৰিব পাৰি৷"</string>
+    <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"এপ্‌টোক কোনো একাউণ্টৰ ছিংক সম্পৰ্কীয় ছেটিংসমূহ সংশোধন কৰিবলৈ অনুমতি দিয়ে৷ উদাহৰণস্বৰূপে, এই কাৰ্যক কোনো একাউণ্টৰ জৰিয়তে People এপ্‌টোৰ ছিংক সক্ষম কৰিবলৈ ব্যৱহাৰ কৰিব পাৰি৷"</string>
     <string name="permlab_readSyncStats" msgid="3747407238320105332">"ছিংকৰ পৰিসংখ্যা পঢ়ক"</string>
     <string name="permdesc_readSyncStats" msgid="3867809926567379434">"ছিংকৰ কাৰ্যক্ৰমসমূহৰ ইতিহাস আৰু ছিংক কৰা ডেটাৰ পৰিমাণসহ কোনো একাউণ্টৰ ছিংকৰ তথ্য পঢ়িবলৈ এপক অনুমতি দিয়ে।"</string>
     <string name="permlab_sdcardRead" msgid="5791467020950064920">"আপোনাৰ শ্বেয়াৰ কৰি ৰখা ষ্ট’ৰেজৰ সমল পঢ়িব পাৰে"</string>
@@ -703,23 +703,23 @@
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"আপোনাৰ শ্বেয়াৰ কৰি ৰখা ষ্ট’ৰেজৰ সমল সংশোধন কৰিব বা মচিব পাৰে"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"আপোনাৰ শ্বেয়াৰ কৰি ৰখা ষ্ট’ৰেজৰ সমল লিখিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP কল কৰা/পোৱা"</string>
-    <string name="permdesc_use_sip" msgid="3590270893253204451">"এপটোক SIP কলসমূহ কৰিবলৈ আৰু পাবলৈ অনুমতি দিয়ে।"</string>
+    <string name="permdesc_use_sip" msgid="3590270893253204451">"এপ্‌টোক SIP কলসমূহ কৰিবলৈ আৰু পাবলৈ অনুমতি দিয়ে।"</string>
     <string name="permlab_register_sim_subscription" msgid="1653054249287576161">"নতুন টেলিকম ছিম সংযোগসমূহ পঞ্জীয়ন কৰা"</string>
-    <string name="permdesc_register_sim_subscription" msgid="4183858662792232464">"এপটোক নতুন টেলিকম সংযোগ পঞ্জীয়ন কৰিবলৈ অনুমতি দিয়ে।"</string>
+    <string name="permdesc_register_sim_subscription" msgid="4183858662792232464">"এপ্‌টোক নতুন টেলিকম সংযোগ পঞ্জীয়ন কৰিবলৈ অনুমতি দিয়ে।"</string>
     <string name="permlab_register_call_provider" msgid="6135073566140050702">"নতুন টেলিকম সংযোগসমূহ পঞ্জীয়ন কৰা"</string>
-    <string name="permdesc_register_call_provider" msgid="4201429251459068613">"এপটোক নতুন টেলিকম সংযোগ পঞ্জীয়ন কৰিবলৈ অনুমতি দিয়ে।"</string>
+    <string name="permdesc_register_call_provider" msgid="4201429251459068613">"এপ্‌টোক নতুন টেলিকম সংযোগ পঞ্জীয়ন কৰিবলৈ অনুমতি দিয়ে।"</string>
     <string name="permlab_connection_manager" msgid="3179365584691166915">"টেলিকম সংযোগ পৰিচালনা কৰা"</string>
-    <string name="permdesc_connection_manager" msgid="1426093604238937733">"এপটোক টেলিকম সংযোগ পৰিচালনা কৰিবলৈ অনুমতি দিয়ে।"</string>
+    <string name="permdesc_connection_manager" msgid="1426093604238937733">"এপ্‌টোক টেলিকম সংযোগ পৰিচালনা কৰিবলৈ অনুমতি দিয়ে।"</string>
     <string name="permlab_bind_incall_service" msgid="5990625112603493016">"ইন-কল স্ক্ৰীনৰ সৈতে সংযোগ স্থাপন"</string>
     <string name="permdesc_bind_incall_service" msgid="4124917526967765162">"ব্যৱহাৰকাৰীগৰাকীয়ে কেতিয়া আৰু কেনেদৰে ইন-কল-স্ক্ৰীন চায় সেয়া নিয়ন্ত্ৰণ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permlab_bind_connection_service" msgid="5409268245525024736">"টেলিফ\'নী সেৱাসমূহৰ সৈতে সংযোগ স্থাপন"</string>
     <string name="permdesc_bind_connection_service" msgid="6261796725253264518">"কল কৰিবলৈ/লাভ কৰিবলৈ টেলিফ\'নী সেৱাসমূহৰ সৈতে এপক সংযোগ স্থাপনৰ বাবে অনুমতি দিয়ে।"</string>
     <string name="permlab_control_incall_experience" msgid="6436863486094352987">"ইন-কল ব্যৱহাৰকাৰীৰ অভিজ্ঞতা প্ৰদান কৰা"</string>
-    <string name="permdesc_control_incall_experience" msgid="5896723643771737534">"এপটোক ইন-কল ব্যৱহাৰকাৰীৰ অভিজ্ঞতা প্ৰদান কৰিবলৈ অনুমতি দিয়ে।"</string>
+    <string name="permdesc_control_incall_experience" msgid="5896723643771737534">"এপ্‌টোক ইন-কল ব্যৱহাৰকাৰীৰ অভিজ্ঞতা প্ৰদান কৰিবলৈ অনুমতি দিয়ে।"</string>
     <string name="permlab_readNetworkUsageHistory" msgid="8470402862501573795">"নেটৱর্কৰ পূৰ্বতে হোৱা ব্যৱহাৰৰ বিষয়ে পঢ়ক"</string>
-    <string name="permdesc_readNetworkUsageHistory" msgid="1112962304941637102">"এপটোক বিশেষ নেটৱৰ্কবিলাকৰ আৰু এপ্‌সমূহৰ নেটৱৰ্ক ব্যৱহাৰৰ ইতিহাস পঢ়িবলৈ অনুমতি দিয়ে।"</string>
+    <string name="permdesc_readNetworkUsageHistory" msgid="1112962304941637102">"এপ্‌টোক বিশেষ নেটৱৰ্কবিলাকৰ আৰু এপ্‌সমূহৰ নেটৱৰ্ক ব্যৱহাৰৰ ইতিহাস পঢ়িবলৈ অনুমতি দিয়ে।"</string>
     <string name="permlab_manageNetworkPolicy" msgid="6872549423152175378">"নেটৱর্কৰ নীতি পৰিচালনা কৰক"</string>
-    <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"এপটোক নেটৱৰ্ক সংযোগৰ নীতিসমূহ পৰিচালনা কৰিবলৈ আৰু এপ্-বিশেষ নিয়ম সংজ্ঞাবদ্ধ কৰিবলৈ অনুমতি দিয়ে।"</string>
+    <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"এপ্‌টোক নেটৱৰ্ক সংযোগৰ নীতিসমূহ পৰিচালনা কৰিবলৈ আৰু এপ্-বিশেষ নিয়ম সংজ্ঞাবদ্ধ কৰিবলৈ অনুমতি দিয়ে।"</string>
     <string name="permlab_modifyNetworkAccounting" msgid="7448790834938749041">"নেটৱর্ক ব্যৱহাৰৰ হিচাপ সলনি কৰক"</string>
     <string name="permdesc_modifyNetworkAccounting" msgid="5076042642247205390">"এপ অনুসুৰি নেটৱর্কৰ ব্যৱহাৰৰ হিচাপ সংশোধন কৰিবলৈ এপক অনুমতি দিয়ে। এয়া সাধাৰণ এপবোৰৰ ব্যৱহাৰৰ বাবে নহয়।"</string>
     <string name="permlab_accessNotifications" msgid="7130360248191984741">"জাননীসমূহ এক্সেছ কৰে"</string>
@@ -747,7 +747,7 @@
     <string name="permlab_bindCarrierServices" msgid="2395596978626237474">"বাহক সেৱাসমূহৰ সৈতে সংযুক্ত হ\'ব পাৰে"</string>
     <string name="permdesc_bindCarrierServices" msgid="9185614481967262900">"বাহক সেৱাৰ সৈতে সংযুক্ত হ\'বলৈ ধাৰকক অনুমতি দিয়ে। সাধাৰণ এপসমূহৰ বাবে সাধাৰণতে প্ৰয়োজন হ\'ব নালাগে।"</string>
     <string name="permlab_access_notification_policy" msgid="5524112842876975537">"অসুবিধা নিদিব চাব পাৰে"</string>
-    <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"অসুবিধা নিদিবৰ কনফিগাৰেশ্বনক পঢ়িবলৈ আৰু সালসলনি কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+    <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"অসুবিধা নিদিবৰ কনফিগাৰেশ্বনক পঢ়িবলৈ আৰু সালসলনি কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"চোৱাৰ অনুমতিৰ ব্যৱহাৰ আৰম্ভ কৰক"</string>
     <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"ধাৰকক কোনো এপৰ বাবে অনুমতিৰ ব্যৱহাৰ আৰম্ভ কৰিবলৈ দিয়ে। সাধাৰণ এপ্‌সমূহৰ বাবে কেতিয়াও প্ৰয়োজন হ’ব নালাগে।"</string>
     <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"অনুমতিৰ সিদ্ধান্তসমূহ চোৱা আৰম্ভ কৰক"</string>
@@ -914,7 +914,7 @@
     <string name="keyguard_password_enter_password_code" msgid="2751130557661643482">"আনলক কৰিবলৈ পাছৱৰ্ড লিখক"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7792964196473964340">"আনলক কৰিবলৈ পিন লিখক"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="8583732939138432793">"ভুল পিন ক\'ড।"</string>
-    <string name="keyguard_label_text" msgid="3841953694564168384">"আনলক কৰিবলৈ মেনু টিপাৰ পিছত ০ টিপক।"</string>
+    <string name="keyguard_label_text" msgid="3841953694564168384">"আনলক কৰিবলৈ মেনু টিপাৰ পাছত ০ টিপক।"</string>
     <string name="emergency_call_dialog_number_for_display" msgid="2978165477085612673">"জৰুৰীকালীন নম্বৰ"</string>
     <string name="lockscreen_carrier_default" msgid="6192313772955399160">"কোনো সেৱা নাই"</string>
     <string name="lockscreen_screen_locked" msgid="7364905540516041817">"স্ক্ৰীন লক কৰা হ’ল।"</string>
@@ -949,12 +949,12 @@
     <string name="lockscreen_sim_puk_locked_instructions" msgid="5307979043730860995">"ব্যৱহাৰকাৰীৰ নিৰ্দেশনা চাওক বা গ্ৰাহক সেৱা কেন্দ্ৰৰ সৈতে যোগাযোগ কৰক।"</string>
     <string name="lockscreen_sim_locked_message" msgid="3160196135801185938">"ছিম কাৰ্ড লক কৰা হৈছে।"</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="2286497117428409709">"ছিম কার্ড আনলক কৰি থকা হৈছে…"</string>
-    <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"আপুনি অশুদ্ধভাৱে আপোনাৰ আনলক আৰ্হি <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ আঁকিছে। \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পিছত পুনৰ চেষ্টা কৰক।"</string>
-    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"আপুনি অশুদ্ধভাৱে আপোনাৰ পাছৱৰ্ড <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পিছত পুনৰ চেষ্টা কৰক।"</string>
-    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"আপুনি অশুদ্ধভাৱে আপোনাৰ পিন <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পিছত পুনৰ চেষ্টা কৰক।"</string>
-    <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"আপুনি অশুদ্ধভাৱে আপোনাৰ লক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ আঁকিলে৷ <xliff:g id="NUMBER_1">%2$d</xliff:g> তকৈ অধিকবাৰ অসফলভাৱে কৰা প্ৰয়াসৰ পিছত, আপোনাৰ ফ\'নটো আনলক কৰিবৰ বাবে Google ছাইন ইনৰ জৰিয়তে কাৰ্যটো কৰিবলৈ কোৱা হ\'ব৷\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পিছত পুনৰ চেষ্টা কৰক৷"</string>
+    <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"আপুনি অশুদ্ধভাৱে আপোনাৰ আনলক আৰ্হি <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ আঁকিছে। \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক।"</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"আপুনি অশুদ্ধভাৱে আপোনাৰ পাছৱৰ্ড <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক।"</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"আপুনি অশুদ্ধভাৱে আপোনাৰ পিন <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক।"</string>
+    <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"আপুনি অশুদ্ধভাৱে আপোনাৰ লক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ আঁকিলে৷ <xliff:g id="NUMBER_1">%2$d</xliff:g> তকৈ অধিকবাৰ অসফলভাৱে কৰা প্ৰয়াসৰ পাছত, আপোনাৰ ফ\'নটো আনলক কৰিবৰ বাবে Google ছাইন ইনৰ জৰিয়তে কাৰ্যটো কৰিবলৈ কোৱা হ\'ব৷\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক৷"</string>
     <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"আপুনি নিজৰ আনলক আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ দিলে। আকৌ <xliff:g id="NUMBER_1">%2$d</xliff:g>বাৰ ভুলকৈ প্ৰয়াস কৰাৰ পাছত আপোনাক নিজৰ Google ছাইন ইন ব্যৱহাৰ কৰি আপোনাৰ Android TV ডিভাইচটো আনলক কৰিবলৈ কোৱা হ’ব।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g>ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক।"</string>
-    <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"আপুনি অশুদ্ধভাৱে আপোনাৰ লক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ আঁকিলে৷ <xliff:g id="NUMBER_1">%2$d</xliff:g> তকৈ অধিকবাৰ অসফলভাৱে কৰা প্ৰয়াসৰ পিছত, আপোনাৰ ফ\'নটো আনলক কৰিবৰ বাবে Google ছাইন ইনৰ জৰিয়তে কাৰ্যটো কৰিবলৈ কোৱা হ\'ব৷\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পিছত পুনৰ চেষ্টা কৰক৷"</string>
+    <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"আপুনি অশুদ্ধভাৱে আপোনাৰ লক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ আঁকিলে৷ <xliff:g id="NUMBER_1">%2$d</xliff:g> তকৈ অধিকবাৰ অসফলভাৱে কৰা প্ৰয়াসৰ পাছত, আপোনাৰ ফ\'নটো আনলক কৰিবৰ বাবে Google ছাইন ইনৰ জৰিয়তে কাৰ্যটো কৰিবলৈ কোৱা হ\'ব৷\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক৷"</string>
     <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"আপুনি টে\'বলেটটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ আনলক কৰিবলৈ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g> বাৰতকৈ বেছি প্ৰয়াস কৰিলে টে\'বলেটটো ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব আৰু আটাইবোৰ ব্যৱহাৰকাৰী ডেটা হেৰুৱাব।"</string>
     <string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="4275591249631864248">"আপুনি নিজৰ Android TV ডিভাইচটো আনলক কৰিবলৈ <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুলকৈ প্ৰয়াস কৰাৰ পাছত আপোনাৰ Android TV ডিভাইচটো ফেক্টৰী ডিফ’ল্টলৈ ৰিছেট কৰা হ’ব আৰু ব্যৱহাৰকাৰীৰ সকলো ডেটা হেৰুৱাব।"</string>
     <string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="1166532464798446579">"আপুনি ফ\'নটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ আনলক কৰিবলৈ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g> বাৰতকৈ বেছি প্ৰয়াস কৰিলে ফ\'নটো ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব আৰু আটাইবোৰ ব্যৱহাৰকাৰী ডেটা হেৰুৱাব।"</string>
@@ -1049,11 +1049,11 @@
     <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"এপ্‌টোক আপোনাৰ Android TV ডিভাইচত ষ্ট’ৰ কৰি ৰখা ব্ৰাউজাৰৰ ইতিহাস আৰু বুকমার্কবোৰ সংশোধন কৰিবলৈ অনুমতি দিয়ে। ব্ৰাউজাৰ ডেটা মোহাৰিবলৈ অথবা সংশোধন কৰিবলৈ ই এপ্‌টোক অনুমতি দিব পাৰে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ অথবা ৱেব ব্ৰাউজিঙৰ ক্ষমতা থকা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ কৰা নহ’বও পাৰে।"</string>
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"আপোনাৰ ফ\'নত সঞ্চয় কৰি ৰখা ব্ৰাউজাৰৰ বুকমার্ক আৰু ব্ৰাউজাৰৰ ইতিহাস সংশোধন কৰিবলৈ এপক অনুমতি দিয়ে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ বা ৱেব ব্ৰাউজিং কৰিব পৰা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ নহ\'বও পাৰে।"</string>
     <string name="permlab_setAlarm" msgid="1158001610254173567">"এলাৰ্ম ছেট কৰক"</string>
-    <string name="permdesc_setAlarm" msgid="2185033720060109640">"এপটোক ইনষ্টল হৈ থকা এলাৰ্ম ক্লক এপত এলাৰ্ম ছেট কৰিবলৈ অনুমতি দিয়ে। কিছুমান এলাৰ্ম ক্লক এপত এই সুবিধাটো প্ৰযোজ্য নহ’ব পাৰে।"</string>
+    <string name="permdesc_setAlarm" msgid="2185033720060109640">"এপ্‌টোক ইনষ্টল হৈ থকা এলাৰ্ম ক্লক এপত এলাৰ্ম ছেট কৰিবলৈ অনুমতি দিয়ে। কিছুমান এলাৰ্ম ক্লক এপত এই সুবিধাটো প্ৰযোজ্য নহ’ব পাৰে।"</string>
     <string name="permlab_addVoicemail" msgid="4770245808840814471">"ভইচমেইল যোগ কৰক"</string>
-    <string name="permdesc_addVoicemail" msgid="5470312139820074324">"আপোনাৰ ভইচমেইল ইনবক্সত বাৰ্তাবোৰ যোগ কৰিবলৈ এপটোক অনুমতি দিয়ক।"</string>
+    <string name="permdesc_addVoicemail" msgid="5470312139820074324">"আপোনাৰ ভইচমেইল ইনবক্সত বাৰ্তাবোৰ যোগ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ক।"</string>
     <string name="permlab_writeGeolocationPermissions" msgid="8605631647492879449">"ব্ৰাউজাৰৰ জিঅ\'লোকেশ্বনৰ অনুমতিসমূহ সংশোধন কৰক"</string>
-    <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"ব্ৰাউজাৰৰ জিঅ\'লোকেশ্বন বিষয়ক অনুমতিসমূহ সংশোধন কৰিবলৈ এপটোক অনুমতি দিয়ে৷ ক্ষতিকাৰক এপবোৰে একপক্ষীয় ৱেবছাইটসমূহলৈ অৱস্থান সেৱাৰ তথ্য পঠিয়াবলৈ ইয়াক ব্যৱহাৰ কৰিব পাৰে৷"</string>
+    <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"ব্ৰাউজাৰৰ জিঅ\'লোকেশ্বন বিষয়ক অনুমতিসমূহ সংশোধন কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে৷ ক্ষতিকাৰক এপবোৰে একপক্ষীয় ৱেবছাইটসমূহলৈ অৱস্থান সেৱাৰ তথ্য পঠিয়াবলৈ ইয়াক ব্যৱহাৰ কৰিব পাৰে৷"</string>
     <string name="save_password_message" msgid="2146409467245462965">"ব্ৰাউজাৰে এই পাছৱর্ডটো মনত ৰখাটো বিচাৰেনে?"</string>
     <string name="save_password_notnow" msgid="2878327088951240061">"এতিয়াই নহয়"</string>
     <string name="save_password_remember" msgid="6490888932657708341">"মনত ৰাখিব"</string>
@@ -1208,12 +1208,12 @@
     <string name="aerr_process" msgid="4268018696970966407">"<xliff:g id="PROCESS">%1$s</xliff:g> বন্ধ হ’ল"</string>
     <string name="aerr_application_repeated" msgid="7804378743218496566">"<xliff:g id="APPLICATION">%1$s</xliff:g> বাৰে বাৰে বন্ধ হৈ গৈছে"</string>
     <string name="aerr_process_repeated" msgid="1153152413537954974">"<xliff:g id="PROCESS">%1$s</xliff:g> বাৰে বাৰে বন্ধ হৈ গৈছে"</string>
-    <string name="aerr_restart" msgid="2789618625210505419">"আকৌ এপটো খোলক"</string>
+    <string name="aerr_restart" msgid="2789618625210505419">"আকৌ এপ্‌টো খোলক"</string>
     <string name="aerr_report" msgid="3095644466849299308">"আপোনাৰ প্ৰতিক্ৰিয়া পঠিয়াওক"</string>
     <string name="aerr_close" msgid="3398336821267021852">"বন্ধ কৰক"</string>
     <string name="aerr_mute" msgid="2304972923480211376">"ডিভাইচ ৰিষ্টাৰ্ট নোহোৱালৈ মিউট কৰক"</string>
     <string name="aerr_wait" msgid="3198677780474548217">"অপেক্ষা কৰক"</string>
-    <string name="aerr_close_app" msgid="8318883106083050970">"এপটো বন্ধ কৰক"</string>
+    <string name="aerr_close_app" msgid="8318883106083050970">"এপ্‌টো বন্ধ কৰক"</string>
     <string name="anr_title" msgid="7290329487067300120"></string>
     <string name="anr_activity_application" msgid="8121716632960340680">"<xliff:g id="APPLICATION">%2$s</xliff:g>য়ে সঁহাৰি দিয়া নাই"</string>
     <string name="anr_activity_process" msgid="3477362583767128667">"<xliff:g id="ACTIVITY">%1$s</xliff:g>য়ে সঁহাৰি দিয়া নাই"</string>
@@ -1231,7 +1231,7 @@
     <string name="screen_compat_mode_hint" msgid="4032272159093750908">"ছিষ্টেমৰ ছেটিং &gt; এপ্‌ &gt; ডাউনল’ড কৰা সমল-লৈ গৈ ইয়াক আকৌ সক্ষম কৰক।"</string>
     <string name="unsupported_display_size_message" msgid="7265211375269394699">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ বর্তমানৰ ডিছপ্লে’ৰ আকাৰ ছেটিং ব্যৱহাৰ কৰিব নোৱাৰে আৰু ই সঠিকভাৱে নচলিবও পাৰে।"</string>
     <string name="unsupported_display_size_show" msgid="980129850974919375">"সদায় দেখুৱাওক"</string>
-    <string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"<xliff:g id="APP_NAME">%1$s</xliff:g>ক এটা খাপ নোখোৱা Android OS সংস্কৰণৰ বাবে তৈয়াৰ কৰা হৈছিল, যাৰ ফলত ই অস্বাভাৱিকধৰণে আচৰণ কৰিব পাৰে। এপটোৰ শেহতীয়া সংস্কৰণ উপলব্ধ হ\'ব পাৰে।"</string>
+    <string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"<xliff:g id="APP_NAME">%1$s</xliff:g>ক এটা খাপ নোখোৱা Android OS সংস্কৰণৰ বাবে তৈয়াৰ কৰা হৈছিল, যাৰ ফলত ই অস্বাভাৱিকধৰণে আচৰণ কৰিব পাৰে। এপ্‌টোৰ শেহতীয়া সংস্কৰণ উপলব্ধ হ\'ব পাৰে।"</string>
     <string name="unsupported_compile_sdk_show" msgid="1601210057960312248">"সদায় দেখুৱাওক"</string>
     <string name="unsupported_compile_sdk_check_update" msgid="1103639989147664456">"আপডে’ট আছে নেকি চাওক"</string>
     <string name="smv_application" msgid="3775183542777792638">"এপটোৱে <xliff:g id="APPLICATION">%1$s</xliff:g> (প্ৰক্ৰিয়াটোৱে <xliff:g id="PROCESS">%2$s</xliff:g>) নিজে বলবৎ কৰা StrictMode নীতি ভংগ কৰিলে।"</string>
@@ -1328,7 +1328,7 @@
     <string name="sms_short_code_confirm_allow" msgid="920477594325526691">"পঠিয়াওক"</string>
     <string name="sms_short_code_confirm_deny" msgid="1356917469323768230">"বাতিল কৰক"</string>
     <string name="sms_short_code_remember_choice" msgid="1374526438647744862">"মোৰ পচন্দ মনত ৰাখিব"</string>
-    <string name="sms_short_code_remember_undo_instruction" msgid="2620984439143080410">"আপুনি ইয়াক পিছত ছেটিং &gt; এপত সলনি কৰিব পাৰে"</string>
+    <string name="sms_short_code_remember_undo_instruction" msgid="2620984439143080410">"আপুনি ইয়াক পাছত ছেটিং &gt; এপত সলনি কৰিব পাৰে"</string>
     <string name="sms_short_code_confirm_always_allow" msgid="2223014893129755950">"যিকোনো সময়ত অনুমতি দিয়ক"</string>
     <string name="sms_short_code_confirm_never_allow" msgid="2688828813521652079">"কেতিয়াও অনুমতি নিদিব"</string>
     <string name="sim_removed_title" msgid="5387212933992546283">"ছিম কাৰ্ড আঁতৰোৱা হ’ল"</string>
@@ -1338,8 +1338,8 @@
     <string name="sim_added_message" msgid="6602906609509958680">"ম\'বাইলৰ নেটৱর্ক ব্যৱহাৰ কৰিবলৈ আপোনাৰ ডিভাইচটো ৰিষ্টার্ট কৰক।"</string>
     <string name="sim_restart_button" msgid="8481803851341190038">"ৰিষ্টাৰ্ট কৰক"</string>
     <string name="install_carrier_app_notification_title" msgid="5712723402213090102">"ম’বাইল সেৱা সক্ৰিয় কৰক"</string>
-    <string name="install_carrier_app_notification_text" msgid="2781317581274192728">"আপোনাৰ নতুন ছিমখন সক্ৰিয় কৰিবলৈ বাহকৰ এপটো ডাউনল’ড কৰক"</string>
-    <string name="install_carrier_app_notification_text_app_name" msgid="4086877327264106484">"আপোনাৰ নতুন ছিমখন সক্ৰিয় কৰিবলৈ <xliff:g id="APP_NAME">%1$s</xliff:g> এপটো ডাউনল’ড কৰক"</string>
+    <string name="install_carrier_app_notification_text" msgid="2781317581274192728">"আপোনাৰ নতুন ছিমখন সক্ৰিয় কৰিবলৈ বাহকৰ এপ্‌টো ডাউনল’ড কৰক"</string>
+    <string name="install_carrier_app_notification_text_app_name" msgid="4086877327264106484">"আপোনাৰ নতুন ছিমখন সক্ৰিয় কৰিবলৈ <xliff:g id="APP_NAME">%1$s</xliff:g> এপ্‌টো ডাউনল’ড কৰক"</string>
     <string name="install_carrier_app_notification_button" msgid="6257740533102594290">"এপ্ ডাউনল’ড কৰক"</string>
     <string name="carrier_app_notification_title" msgid="5815477368072060250">"নতুন ছিম ভৰোৱা হৈছে"</string>
     <string name="carrier_app_notification_text" msgid="6567057546341958637">"ছেট আপ কৰিবলৈ টিপক"</string>
@@ -1452,9 +1452,9 @@
     <string name="permlab_readInstallSessions" msgid="7279049337895583621">"ইনষ্টল কৰা ছেশ্বনসমূহ পঢ়িব পাৰে"</string>
     <string name="permdesc_readInstallSessions" msgid="4012608316610763473">"এটা এপ্লিকেশ্বনক ইনষ্টল কৰা ছেশ্বনসমূহ পঢ়িবলৈ অনুমতি দিয়ে। এই কাৰ্যই সক্ৰিয় পেকেজ ইনষ্টলেশ্বনৰ বিষয়ে চাবলৈ অনুমতি দিয়ে।"</string>
     <string name="permlab_requestInstallPackages" msgid="7600020863445351154">"পেকেজ ইনষ্টলৰ বাবে অনুৰোধ কৰিব পাৰে"</string>
-    <string name="permdesc_requestInstallPackages" msgid="3969369278325313067">"পেকেজ ইনষ্টল কৰাৰ অনুৰোধ প্ৰেৰণ কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+    <string name="permdesc_requestInstallPackages" msgid="3969369278325313067">"পেকেজ ইনষ্টল কৰাৰ অনুৰোধ প্ৰেৰণ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permlab_requestDeletePackages" msgid="2541172829260106795">"পেকেজ মচাৰ অনুৰোধ কৰিব পাৰে"</string>
-    <string name="permdesc_requestDeletePackages" msgid="6133633516423860381">"এপটোক পেকেজবোৰ মচাৰ অনুৰোধ কৰিবলৈ দিয়ে।"</string>
+    <string name="permdesc_requestDeletePackages" msgid="6133633516423860381">"এপ্‌টোক পেকেজবোৰ মচাৰ অনুৰোধ কৰিবলৈ দিয়ে।"</string>
     <string name="permlab_requestIgnoreBatteryOptimizations" msgid="7646611326036631439">"বেটাৰী অপ্টিমাইজেশ্বন উপেক্ষা কৰিবলৈ বিচাৰক"</string>
     <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"কোনো এপক সেই এপ্‌টোৰ বাবে বেটাৰী অপ্টিমাইজেশ্বন উপেক্ষা কৰিবলৈ অনুমতি বিচাৰিবলৈ দিয়ে।"</string>
     <string name="permlab_queryAllPackages" msgid="2928450604653281650">"আটাইবোৰ পেকেজত প্ৰশ্ন সোধক"</string>
@@ -1478,8 +1478,8 @@
     <string name="permission_request_notification_title" msgid="1810025922441048273">"অনুমতি বিচাৰি অনুৰোধ কৰা হৈছে"</string>
     <string name="permission_request_notification_with_subtitle" msgid="3743417870360129298">"<xliff:g id="ACCOUNT">%s</xliff:g> একাউণ্টৰ বাবে\nঅনুমতি বিচাৰি অনুৰোধ কৰা হৈছে।"</string>
     <string name="permission_request_notification_for_app_with_subtitle" msgid="1298704005732851350">"<xliff:g id="APP">%1$s</xliff:g>এ <xliff:g id="ACCOUNT">%2$s</xliff:g> একাউণ্টটো এক্সেছৰ \nঅনুমতি বিচাৰি অনুৰোধ জনাইছে।"</string>
-    <string name="forward_intent_to_owner" msgid="4620359037192871015">"আপুনি আপোনাৰ কৰ্মস্থানৰ প্ৰ\'ফাইলৰ বাহিৰত এই এপটো ব্যৱহাৰ কৰি আছে"</string>
-    <string name="forward_intent_to_work" msgid="3620262405636021151">"আপুনি আপোনাৰ কৰ্মস্থানৰ প্ৰ\'ফাইলৰ ভিতৰত এই এপটো ব্যৱহাৰ কৰি আছে"</string>
+    <string name="forward_intent_to_owner" msgid="4620359037192871015">"আপুনি আপোনাৰ কৰ্মস্থানৰ প্ৰ\'ফাইলৰ বাহিৰত এই এপ্‌টো ব্যৱহাৰ কৰি আছে"</string>
+    <string name="forward_intent_to_work" msgid="3620262405636021151">"আপুনি আপোনাৰ কৰ্মস্থানৰ প্ৰ\'ফাইলৰ ভিতৰত এই এপ্‌টো ব্যৱহাৰ কৰি আছে"</string>
     <string name="input_method_binding_label" msgid="1166731601721983656">"ইনপুট পদ্ধতি"</string>
     <string name="sync_binding_label" msgid="469249309424662147">"ছিংক"</string>
     <string name="accessibility_binding_label" msgid="1974602776545801715">"সাধ্য সুবিধাসমূহ"</string>
@@ -1560,7 +1560,7 @@
     <string name="shareactionprovider_share_with" msgid="2753089758467748982">"ইয়াৰ জৰিয়তে শ্বেয়াৰ কৰক"</string>
     <string name="shareactionprovider_share_with_application" msgid="4902832247173666973">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>ৰ জৰিয়তে শ্বেয়াৰ কৰক"</string>
     <string name="content_description_sliding_handle" msgid="982510275422590757">"শ্লাইড কৰা হেণ্ডেল৷ স্পৰ্শ কৰক আৰু ধৰি ৰাখক৷"</string>
-    <string name="description_target_unlock_tablet" msgid="7431571180065859551">"স্ক্ৰীণ আনলক কৰিবলৈ ছোৱাইপ কৰক৷"</string>
+    <string name="description_target_unlock_tablet" msgid="7431571180065859551">"স্ক্ৰীন আনলক কৰিবলৈ ছোৱাইপ কৰক৷"</string>
     <string name="action_bar_home_description" msgid="1501655419158631974">"গৃহ পৃষ্ঠালৈ যাওক"</string>
     <string name="action_bar_up_description" msgid="6611579697195026932">"ওপৰলৈ যাওক"</string>
     <string name="action_menu_overflow_description" msgid="4579536843510088170">"অধিক বিকল্প"</string>
@@ -1660,18 +1660,18 @@
     <string name="kg_login_invalid_input" msgid="8292367491901220210">"ব্যৱহাৰকাৰীৰ অমান্য নাম বা পাছৱর্ড।"</string>
     <string name="kg_login_account_recovery_hint" msgid="4892466171043541248">"নিজৰ ব্যৱহাৰকাৰী নাম আৰু পাছৱর্ড পাহৰিলেনে?\n"<b>"google.com/accounts/recovery"</b>" লৈ যাওক।"</string>
     <string name="kg_login_checking_password" msgid="4676010303243317253">"একাউণ্ট পৰীক্ষা কৰি থকা হৈছে…"</string>
-    <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="23741434207544038">"আপুনি আপোনাৰ পিন <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string>
-    <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="3328686432962224215">"আপুনি আপোনাৰ পাছৱৰ্ড <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string>
-    <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="7357404233979139075">"আপুনি আপোনাৰ ল\'ক খোলাৰ আৰ্হি <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ আঁকিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string>
+    <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="23741434207544038">"আপুনি আপোনাৰ পিন <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string>
+    <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="3328686432962224215">"আপুনি আপোনাৰ পাছৱৰ্ড <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string>
+    <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="7357404233979139075">"আপুনি আপোনাৰ ল\'ক খোলাৰ আৰ্হি <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ আঁকিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string>
     <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="3479940221343361587">"আপুনি <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ টেবলেটৰ ল\'ক খোলাৰ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল প্ৰয়াস কৰিলে টেবলেটটো ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব আৰু আটাইবোৰ ব্যৱহাৰকাৰীৰ ডেটা হেৰুৱাব।"</string>
     <string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="9064457748587850217">"আপুনি নিজৰ Android TV ডিভাইচটো আনলক কৰিবলৈ <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুলকৈ প্ৰয়াস কৰাৰ পাছত আপোনাৰ Android TV ডিভাইচটো ফেক্টৰী ডিফ’ল্টলৈ ৰিছেট কৰা হ’ব আৰু ব্যৱহাৰকাৰীৰ আটাইবোৰ ডেটা হেৰুৱাব।"</string>
     <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="5955398963754432548">"আপুনি <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ ফ\'নৰ ল\'ক খোলাৰ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল প্ৰয়াস কৰিলে ফ\'নটো ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব আৰু ব্যৱহাৰকাৰীৰ আটাইবোৰ ডেটা হেৰুৱাব।"</string>
     <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2299099385175083308">"আপুনি <xliff:g id="NUMBER">%d</xliff:g>বাৰ ভুলকৈ টেবলেটৰ ল\'ক খোলাৰ প্ৰয়াস কৰিছে। টেবলেটটো এতিয়া ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব।"</string>
     <string name="kg_failed_attempts_now_wiping" product="tv" msgid="5045460916106267585">"আপুনি নিজৰ Android TV ডিভাইচটো আনলক কৰিবলৈ <xliff:g id="NUMBER">%d</xliff:g>বাৰ ভুলকৈ প্ৰয়াস কৰিছে। আপোনাৰ Android TV ডিভাইচটো এতিয়া ফেক্টৰী ডিফ’ল্টলৈ ৰিছেট কৰা হ’ব।"</string>
     <string name="kg_failed_attempts_now_wiping" product="default" msgid="5043730590446071189">"আপুনি <xliff:g id="NUMBER">%d</xliff:g>বাৰ ভুলকৈ ফ\'নৰ ল\'ক খোলাৰ প্ৰয়াস কৰিছে। ফ\'নটো এতিয়া ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব।"</string>
-    <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"আপুনি আপোনাৰ ল\'ক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ আঁকিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল আৰ্হি আঁকিলে আপোনাৰ টেবলেটটো কোনো একাউণ্টৰ জৰিয়তে আনলক কৰিবলৈ কোৱা হ\'ব।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string>
+    <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"আপুনি আপোনাৰ ল\'ক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ আঁকিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল আৰ্হি আঁকিলে আপোনাৰ টেবলেটটো কোনো একাউণ্টৰ জৰিয়তে আনলক কৰিবলৈ কোৱা হ\'ব।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string>
     <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"আপুনি নিজৰ আনলক আর্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ দিয়ে। আকৌ <xliff:g id="NUMBER_1">%2$d</xliff:g>বাৰ ভুলকৈ প্ৰয়াস কৰাৰ পাছত আপোনাক এটা ইমেইল একাউণ্ট ব্যৱহাৰ কৰি নিজৰ Android TV ডিভাইচটো আনলক কৰিবলৈ কোৱা হ’ব।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g>ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক।"</string>
-    <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"আপুনি আপোনাৰ ল\'ক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ আঁকিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল আৰ্হি আঁকিলে আপোনাৰ ফ\'নটো কোনো একাউণ্টৰ জৰিয়তে আনলক কৰিবলৈ কোৱা হ\'ব।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string>
+    <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"আপুনি আপোনাৰ ল\'ক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ আঁকিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল আৰ্হি আঁকিলে আপোনাৰ ফ\'নটো কোনো একাউণ্টৰ জৰিয়তে আনলক কৰিবলৈ কোৱা হ\'ব।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string>
     <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string>
     <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"আঁতৰাওক"</string>
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"অনুমোদিত স্তৰতকৈ ওপৰলৈ ভলিউম বঢ়াব নেকি?\n\nদীৰ্ঘ সময়ৰ বাবে উচ্চ ভলিউমত শুনাৰ ফলত শ্ৰৱণ ক্ষমতাৰ ক্ষতি হ\'ব পাৰে।"</string>
@@ -1835,7 +1835,7 @@
     <string name="restr_pin_create_pin" msgid="917067613896366033">"সীমাবদ্ধতা সংশোধন কৰিবলৈ এটা পিন সৃষ্টি কৰক"</string>
     <string name="restr_pin_error_doesnt_match" msgid="7063392698489280556">"পিনবোৰ মিলা নাই। আকৌ চেষ্টা কৰক।"</string>
     <string name="restr_pin_error_too_short" msgid="1547007808237941065">"পিনটো অতি চুটি। কমেও ৪টা সংখ্যাৰ হ\'ব লাগিব।"</string>
-    <string name="restr_pin_try_later" msgid="5897719962541636727">"পিছত আকৌ চেষ্টা কৰক"</string>
+    <string name="restr_pin_try_later" msgid="5897719962541636727">"পাছত আকৌ চেষ্টা কৰক"</string>
     <string name="immersive_cling_title" msgid="2307034298721541791">"স্ক্ৰীন পূৰ্ণৰূপত চাই আছে"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"বাহিৰ হ\'বলৈ ওপৰৰপৰা তললৈ ছোৱাইপ কৰক।"</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"বুজি পালোঁ"</string>
@@ -1931,7 +1931,7 @@
     <string name="language_picker_section_all" msgid="1985809075777564284">"সকলো ভাষা"</string>
     <string name="region_picker_section_all" msgid="756441309928774155">"আটাইবোৰ অঞ্চল"</string>
     <string name="locale_search_menu" msgid="6258090710176422934">"সন্ধান কৰক"</string>
-    <string name="app_suspended_title" msgid="888873445010322650">"এপটো নাই"</string>
+    <string name="app_suspended_title" msgid="888873445010322650">"এপ্‌টো নাই"</string>
     <string name="app_suspended_default_message" msgid="6451215678552004172">"এই মুহূৰ্তত <xliff:g id="APP_NAME_0">%1$s</xliff:g> উপলব্ধ নহয়। ইয়াক <xliff:g id="APP_NAME_1">%2$s</xliff:g>এ পৰিচালনা কৰে।"</string>
     <string name="app_suspended_more_details" msgid="211260942831587014">"অধিক জানক"</string>
     <string name="app_suspended_unsuspend_message" msgid="1665438589450555459">"এপ্‌ আনপজ কৰক"</string>
@@ -1958,7 +1958,7 @@
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"এইটো আপোনাৰ <xliff:g id="DEVICE">%1$s</xliff:g>ত এক্সেছ কৰিব নোৱাৰি। তাৰ পৰিৱৰ্তে আপোনাৰ Android TV ডিভাইচত চেষ্টা কৰি চাওক।"</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"এইটো আপোনাৰ <xliff:g id="DEVICE">%1$s</xliff:g>ত এক্সেছ কৰিব নোৱাৰি। তাৰ পৰিৱৰ্তে আপোনাৰ টেবলেটত চেষ্টা কৰি চাওক।"</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"এইটো আপোনাৰ <xliff:g id="DEVICE">%1$s</xliff:g>ত এক্সেছ কৰিব নোৱাৰি। তাৰ পৰিৱৰ্তে আপোনাৰ ফ’নত চেষ্টা কৰি চাওক।"</string>
-    <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"এই এপটো Androidৰ এটা পুৰণা সংস্কৰণৰ বাবে প্ৰস্তুত কৰা হৈছিল, আৰু ই বিচৰাধৰণে কাম নকৰিবও পাৰে। ইয়াৰ আপডে’ট আছে নেকি চাওক, বা বিকাশকৰ্তাৰ সৈতে যোগাযোগ কৰক।"</string>
+    <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"এই এপ্‌টো Androidৰ এটা পুৰণা সংস্কৰণৰ বাবে প্ৰস্তুত কৰা হৈছিল, আৰু ই বিচৰাধৰণে কাম নকৰিবও পাৰে। ইয়াৰ আপডে’ট আছে নেকি চাওক, বা বিকাশকৰ্তাৰ সৈতে যোগাযোগ কৰক।"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"আপডে’ট আছে নেকি চাওক"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"আপুনি নতুন বার্তা লাভ কৰিছে"</string>
     <string name="new_sms_notification_content" msgid="3197949934153460639">"চাবলৈ এছএমএছ এপ্ খোলক"</string>
@@ -1997,7 +1997,7 @@
     <string name="time_picker_text_input_mode_description" msgid="4761160667516611576">"সময়ৰ ইনপুটৰ বাবে পাঠৰ ইনপুট ম\'ডলৈ যাওক।"</string>
     <string name="time_picker_radial_mode_description" msgid="1222342577115016953">"সময়ৰ ইনপুটৰ বাবে ঘড়ী ম\'ডলৈ যাওক।"</string>
     <string name="autofill_picker_accessibility_title" msgid="4425806874792196599">"স্বয়ংপূৰ্তিৰ বিকল্পসমূহ"</string>
-    <string name="autofill_save_accessibility_title" msgid="1523225776218450005">"পিছত স্বয়ংপূৰ্তি কৰিবলৈ ছেভ কৰক"</string>
+    <string name="autofill_save_accessibility_title" msgid="1523225776218450005">"পাছত স্বয়ংপূৰ্তি কৰিবলৈ ছেভ কৰক"</string>
     <string name="autofill_error_cannot_autofill" msgid="6528827648643138596">"সমলসমূহ স্বয়ংপূৰ্তি কৰিব নোৱাৰি"</string>
     <string name="autofill_picker_no_suggestions" msgid="1076022650427481509">"কোনো স্বয়ংপূৰ্তি পৰামৰ্শ নাই"</string>
     <string name="autofill_picker_some_suggestions" msgid="5560549696296202701">"{count,plural, =1{এটা স্বয়ংপূৰ্তি পৰামৰ্শ}one{# টা স্বয়ংপূৰ্তি পৰামৰ্শ}other{# টা স্বয়ংপূৰ্তি পৰামৰ্শ}}"</string>
@@ -2040,7 +2040,7 @@
     <string name="popup_window_default_title" msgid="6907717596694826919">"পপআপ ৱিণ্ড\'"</string>
     <string name="slice_more_content" msgid="3377367737876888459">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
     <string name="shortcut_restored_on_lower_version" msgid="9206301954024286063">"এপৰ সংস্কৰণ অৱনমিত কৰা হৈছে, বা ই এই শ্বৰ্টকাটটোৰ লগত খাপ নাখায়"</string>
-    <string name="shortcut_restore_not_supported" msgid="4763198938588468400">"এপটোত বেকআপ আৰু পুনঃস্থাপন সুবিধা নথকাৰ বাবে শ্বৰ্টকাট পুনঃস্থাপন কৰিবপৰা নগ\'ল"</string>
+    <string name="shortcut_restore_not_supported" msgid="4763198938588468400">"এপ্‌টোত বেকআপ আৰু পুনঃস্থাপন সুবিধা নথকাৰ বাবে শ্বৰ্টকাট পুনঃস্থাপন কৰিবপৰা নগ\'ল"</string>
     <string name="shortcut_restore_signature_mismatch" msgid="579345304221605479">"এপৰ স্বাক্ষৰৰ অমিল হোৱাৰ বাবে শ্বৰ্টকাট পুনঃস্থাপন কৰিবপৰা নগ\'ল"</string>
     <string name="shortcut_restore_unknown_issue" msgid="2478146134395982154">"শ্বৰ্টকাট পুনঃস্থাপন কৰিবপৰা নগ\'ল"</string>
     <string name="shortcut_disabled_reason_unknown" msgid="753074793553599166">"শ্বৰ্টকাট অক্ষম কৰি থোৱা হৈছে"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index b52e57a..4f6d0c7 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1949,15 +1949,15 @@
     <string name="app_streaming_blocked_title_for_settings_dialog" product="tv" msgid="196994247017450357">"La configuració d\'Android TV no està disponible"</string>
     <string name="app_streaming_blocked_title_for_settings_dialog" product="tablet" msgid="8222710146267948647">"La configuració de la tauleta no està disponible"</string>
     <string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"Configuració del telèfon no disponible"</string>
-    <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"En aquests moments, no es pot accedir a aquesta aplicació al dispositiu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al dispositiu Android TV."</string>
-    <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"En aquests moments, no es pot accedir a aquesta aplicació al dispositiu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho a la tauleta."</string>
-    <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"No es pot accedir a aquesta aplicació al teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al telèfon."</string>
+    <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"En aquests moments, No s\'hi pot accedir des del teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al dispositiu Android TV."</string>
+    <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"En aquests moments, No s\'hi pot accedir des del teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho a la tauleta."</string>
+    <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"No s\'hi pot accedir des del teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al telèfon."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Aquesta aplicació sol·licita seguretat addicional. Prova-ho al dispositiu Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Aquesta aplicació sol·licita seguretat addicional. Prova-ho a la tauleta."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Aquesta aplicació sol·licita seguretat addicional. Prova-ho al telèfon."</string>
-    <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"No s\'hi pot accedir des del dispositiu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al dispositiu Android TV."</string>
-    <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"No s\'hi pot accedir des del dispositiu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho a la tauleta."</string>
-    <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"No s\'hi pot accedir des del dispositiu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al telèfon."</string>
+    <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"No s\'hi pot accedir des del teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al dispositiu Android TV."</string>
+    <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"No s\'hi pot accedir des del teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho a la tauleta."</string>
+    <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"No s\'hi pot accedir des del teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al telèfon."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Aquesta aplicació es va crear per a una versió antiga d\'Android i pot ser que no funcioni correctament. Prova de cercar actualitzacions o contacta amb el desenvolupador."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Cerca actualitzacions"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Tens missatges nous"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 2a3f916..e8e5a51 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1970,8 +1970,8 @@
     <string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Presiona para ver archivos"</string>
     <string name="pin_target" msgid="8036028973110156895">"Fijar"</string>
     <string name="pin_specific_target" msgid="7824671240625957415">"Fijar <xliff:g id="LABEL">%1$s</xliff:g>"</string>
-    <string name="unpin_target" msgid="3963318576590204447">"No fijar"</string>
-    <string name="unpin_specific_target" msgid="3859828252160908146">"No fijar <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="unpin_target" msgid="3963318576590204447">"Dejar de fijar"</string>
+    <string name="unpin_specific_target" msgid="3859828252160908146">"Dejar de fijar <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="app_info" msgid="6113278084877079851">"Información de apps"</string>
     <string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="demo_starting_message" msgid="6577581216125805905">"Iniciando demostración…"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 90f1006..fcbe2b8 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1956,9 +1956,9 @@
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Esta aplicación solicita seguridad adicional. Prueba en tu dispositivo Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Esta aplicación solicita seguridad adicional. Prueba en tu tablet."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Esta aplicación solicita seguridad adicional. Prueba en tu teléfono."</string>
-    <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"No se puede acceder a este contenido en tu <xliff:g id="DEVICE">%1$s</xliff:g>. Prueba en tu dispositivo Android TV."</string>
+    <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"No se puede acceder desde tu <xliff:g id="DEVICE">%1$s</xliff:g>. Prueba en tu dispositivo Android TV."</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"No se puede acceder a este contenido en tu <xliff:g id="DEVICE">%1$s</xliff:g>. Prueba en tu tablet."</string>
-    <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"No se puede acceder a este contenido en tu <xliff:g id="DEVICE">%1$s</xliff:g>. Prueba en tu teléfono."</string>
+    <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"No se puede acceder desde tu <xliff:g id="DEVICE">%1$s</xliff:g>. Prueba en tu teléfono."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Esta aplicación se ha diseñado para una versión anterior de Android y es posible que no funcione correctamente. Busca actualizaciones o ponte en contacto con el desarrollador."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Buscar actualizaciones"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Tienes mensajes nuevos"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 15b25ad..681cc91 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -763,10 +763,10 @@
     <string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"Kontrolatu zenbat aldiz idatzi duzun oker pasahitza pantaila desblokeatzen saiatzean, eta blokeatu Android TV gailua edo ezabatu bertako datu guztiak pasahitza gehiegitan idazten baduzu oker."</string>
     <string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"Kontrolatu zenbatetan idazten duzun pasahitza oker pantaila desblokeatzen saiatzean eta, gehiegitan idazten bada oker, blokeatu informazio- eta aisia-sistema edo ezabatu hango eduki guztia."</string>
     <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"Kontrolatu pantaila desblokeatzen saiatzean idatzitako pasahitz oker kopurua, eta blokeatu telefonoa edo ezabatu bere datuak pasahitza gehiegitan oker idazten bada."</string>
-    <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="2049038943004297474">"Kontrolatu pantaila desblokeatzen saiatzean idatzitako pasahitz oker kopurua, eta blokeatu tableta edo ezabatu erabiltzailearen datuak pasahitza gehiegitan oker idazten bada."</string>
-    <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"Kontrolatu zenbat aldiz idatzi duzun oker pasahitza pantaila desblokeatzen saiatzean, eta blokeatu Android TV gailua edo ezabatu erabiltzailearen datuak pasahitza gehiegitan idazten baduzu oker."</string>
+    <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="2049038943004297474">"Kontrolatu pantaila desblokeatzen saiatzean idatzitako pasahitz oker kopurua, eta blokeatu tableta edo ezabatu erabiltzaile-datuak pasahitza gehiegitan oker idazten bada."</string>
+    <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"Kontrolatu zenbat aldiz idatzi duzun oker pasahitza pantaila desblokeatzen saiatzean, eta blokeatu Android TV gailua edo ezabatu erabiltzaile-datuak pasahitza gehiegitan idazten baduzu oker."</string>
     <string name="policydesc_watchLogin_secondaryUser" product="automotive" msgid="7180857406058327941">"Kontrolatu zenbatetan idazten duzun pasahitza oker pantaila desblokeatzen saiatzean eta, gehiegitan idazten bada oker, blokeatu informazio- eta aisia-sistema edo ezabatu profil honetako eduki guztia."</string>
-    <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="9177645136475155924">"Kontrolatu pantaila desblokeatzen saiatzean idatzitako pasahitz oker kopurua, eta blokeatu telefonoa edo ezabatu erabiltzailearen datuak pasahitza gehiegitan oker idazten bada."</string>
+    <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="9177645136475155924">"Kontrolatu pantaila desblokeatzen saiatzean idatzitako pasahitz oker kopurua, eta blokeatu telefonoa edo ezabatu erabiltzaile-datuak pasahitza gehiegitan oker idazten bada."</string>
     <string name="policylab_resetPassword" msgid="214556238645096520">"Aldatu pantailaren blokeoa"</string>
     <string name="policydesc_resetPassword" msgid="4626419138439341851">"Aldatu pantailaren blokeoa."</string>
     <string name="policylab_forceLock" msgid="7360335502968476434">"Blokeatu pantaila"</string>
@@ -777,7 +777,7 @@
     <string name="policydesc_wipeData" product="automotive" msgid="660804547737323300">"Berrezarri informazio- eta aisia-sistemako jatorrizko datuak abisatu gabe, bertan zegoen eduki guztia ezabatzeko."</string>
     <string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"Ezabatu telefonoaren datuak abisatu gabe, jatorrizko datuak berrezarrita."</string>
     <string name="policylab_wipeData_secondaryUser" product="automotive" msgid="115034358520328373">"Ezabatu profileko eduki guztia"</string>
-    <string name="policylab_wipeData_secondaryUser" product="default" msgid="413813645323433166">"Ezabatu erabiltzailearen datuak"</string>
+    <string name="policylab_wipeData_secondaryUser" product="default" msgid="413813645323433166">"Ezabatu erabiltzaile-datuak"</string>
     <string name="policydesc_wipeData_secondaryUser" product="tablet" msgid="2336676480090926470">"Ezabatu erabiltzaileak tabletan dituen datuak abisatu gabe."</string>
     <string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2293713284515865200">"Ezabatu erabiltzaileak Android TV gailuan dituen datuak abisatu gabe."</string>
     <string name="policydesc_wipeData_secondaryUser" product="automotive" msgid="4658832487305780879">"Ezabatu informazio- eta aisia-sisteman dagoen profil honetako eduki guztia abisatu gabe."</string>
@@ -1134,7 +1134,7 @@
     <string name="Midnight" msgid="8176019203622191377">"Gauerdia"</string>
     <string name="elapsed_time_short_format_mm_ss" msgid="8689459651807876423">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="2302144714803345056">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
-    <string name="selectAll" msgid="1532369154488982046">"Hautatu guztiak"</string>
+    <string name="selectAll" msgid="1532369154488982046">"Hautatu dena"</string>
     <string name="cut" msgid="2561199725874745819">"Ebaki"</string>
     <string name="copy" msgid="5472512047143665218">"Kopiatu"</string>
     <string name="failed_to_copy_to_clipboard" msgid="725919885138539875">"Ezin izan da kopiatu arbelean"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 104808b..b86ce04 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1403,7 +1403,7 @@
     <string name="ext_media_checking_notification_message" product="tv" msgid="7986154434946021415">"Analyse de l\'espace de stockage sur le support en cours…"</string>
     <string name="ext_media_new_notification_title" msgid="3517407571407687677">"Nouveau périphérique <xliff:g id="NAME">%s</xliff:g>"</string>
     <string name="ext_media_new_notification_title" product="automotive" msgid="9085349544984742727">"<xliff:g id="NAME">%s</xliff:g> ne fonctionne pas"</string>
-    <string name="ext_media_new_notification_message" msgid="6095403121990786986">"Toucher pour configurer"</string>
+    <string name="ext_media_new_notification_message" msgid="6095403121990786986">"Touchez pour configurer"</string>
     <string name="ext_media_new_notification_message" product="tv" msgid="216863352100263668">"Sélectionnez pour configurer"</string>
     <string name="ext_media_new_notification_message" product="automotive" msgid="5140127881613227162">"Vous devrez peut-être reformater l\'appareil. Touchez pour l\'éjecter."</string>
     <string name="ext_media_ready_notification_message" msgid="7509496364380197369">"Pour stocker des photos, des vidéos, de la musique et plus encore"</string>
@@ -1415,7 +1415,7 @@
     <string name="ext_media_unmountable_notification_message" product="automotive" msgid="2274596120715020680">"Vous devrez peut-être reformater l\'appareil. Touchez pour l\'éjecter."</string>
     <string name="ext_media_unsupported_notification_title" msgid="3487534182861251401">"<xliff:g id="NAME">%s</xliff:g> détecté"</string>
     <string name="ext_media_unsupported_notification_title" product="automotive" msgid="6004193172658722381">"<xliff:g id="NAME">%s</xliff:g> ne fonctionne pas"</string>
-    <string name="ext_media_unsupported_notification_message" msgid="8463636521459807981">"Toucher pour configurer ."</string>
+    <string name="ext_media_unsupported_notification_message" msgid="8463636521459807981">"Touchez pour configurer ."</string>
     <string name="ext_media_unsupported_notification_message" product="tv" msgid="1595482802187036532">"Sélectionner pour configurer <xliff:g id="NAME">%s</xliff:g> dans un format pris en charge."</string>
     <string name="ext_media_unsupported_notification_message" product="automotive" msgid="3412494732736336330">"Vous devrez peut-être reformater l\'appareil"</string>
     <string name="ext_media_badremoval_notification_title" msgid="4114625551266196872">"Retrait inattendu de la mémoire « <xliff:g id="NAME">%s</xliff:g> »"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 7f099b0..f8e9efb 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -580,7 +580,7 @@
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Utilisez la biométrie ou le verrouillage de l\'écran pour continuer"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Matériel biométrique indisponible"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentification annulée"</string>
-    <string name="biometric_not_recognized" msgid="5106687642694635888">"Non reconnu"</string>
+    <string name="biometric_not_recognized" msgid="5106687642694635888">"Non reconnue"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentification annulée"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Aucun code, schéma ni mot de passe n\'est défini"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Erreur d\'authentification"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 79faaac..0daa324 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1139,7 +1139,7 @@
     <string name="copy" msgid="5472512047143665218">"कॉपी करें"</string>
     <string name="failed_to_copy_to_clipboard" msgid="725919885138539875">"क्लिपबोर्ड पर कॉपी नहीं हो सका"</string>
     <string name="paste" msgid="461843306215520225">"चिपकाएं"</string>
-    <string name="paste_as_plain_text" msgid="7664800665823182587">"सादे पाठ के रूप में चिपकाएं"</string>
+    <string name="paste_as_plain_text" msgid="7664800665823182587">"सादे टेक्स्ट के रूप में चिपकाएं"</string>
     <string name="replace" msgid="7842675434546657444">"बदलें•"</string>
     <string name="delete" msgid="1514113991712129054">"मिटाएं"</string>
     <string name="copyUrl" msgid="6229645005987260230">"यूआरएल को कॉपी करें"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index fb95a1f..73cbf93 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1210,7 +1210,7 @@
     <string name="aerr_application_repeated" msgid="7804378743218496566">"Aplikacija <xliff:g id="APPLICATION">%1$s</xliff:g> neprekidno se ruši"</string>
     <string name="aerr_process_repeated" msgid="1153152413537954974">"Postupak <xliff:g id="PROCESS">%1$s</xliff:g> neprekidno se ruši"</string>
     <string name="aerr_restart" msgid="2789618625210505419">"Ponovo otvori aplikaciju"</string>
-    <string name="aerr_report" msgid="3095644466849299308">"Pošalji povratne informacije"</string>
+    <string name="aerr_report" msgid="3095644466849299308">"Pošaljite povratne informacije"</string>
     <string name="aerr_close" msgid="3398336821267021852">"Zatvori"</string>
     <string name="aerr_mute" msgid="2304972923480211376">"Zanemari do ponovnog pokretanja uređaja"</string>
     <string name="aerr_wait" msgid="3198677780474548217">"Čekaj"</string>
@@ -1951,14 +1951,14 @@
     <string name="app_streaming_blocked_title_for_settings_dialog" product="tablet" msgid="8222710146267948647">"Postavke tableta nisu dostupne"</string>
     <string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"Postavke telefona nisu dostupne"</string>
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Trenutačno toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na Android TV uređaju."</string>
-    <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Trenutačno toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na svojem tabletu."</string>
-    <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Trenutačno toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na svojem telefonu."</string>
+    <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Trenutačno toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na tabletu."</string>
+    <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Trenutačno toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na telefonu."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Ta aplikacija zahtijeva dodatnu sigurnost. Pokušajte joj pristupiti na Android TV uređaju."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Ta aplikacija zahtijeva dodatnu sigurnost. Pokušajte joj pristupiti na tabletu."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Ta aplikacija zahtijeva dodatnu sigurnost. Pokušajte joj pristupiti na telefonu."</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na Android TV uređaju."</string>
-    <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na svojem tabletu."</string>
-    <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na svojem telefonu."</string>
+    <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na tabletu."</string>
+    <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na telefonu."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ova je aplikacija razvijena za stariju verziju Androida i možda neće funkcionirati pravilno. Potražite ažuriranja ili se obratite razvojnom programeru."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Provjeri ažuriranja"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Imate nove poruke"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 9ab64e6..d11eca6 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -252,7 +252,7 @@
     <string name="bugreport_message" msgid="5212529146119624326">"현재 기기 상태에 대한 정보를 수집하여 이메일 메시지로 전송합니다. 버그 신고를 시작하여 전송할 준비가 되려면 약간 시간이 걸립니다."</string>
     <string name="bugreport_option_interactive_title" msgid="7968287837902871289">"대화형 보고서"</string>
     <string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"대부분의 경우 이 옵션을 사용합니다. 신고 진행 상황을 추적하고 문제에 대한 세부정보를 입력하고 스크린샷을 찍을 수 있습니다. 신고하기에 시간이 너무 오래 걸리고 사용 빈도가 낮은 일부 섹션을 생략할 수 있습니다."</string>
-    <string name="bugreport_option_full_title" msgid="7681035745950045690">"전체 보고서"</string>
+    <string name="bugreport_option_full_title" msgid="7681035745950045690">"전체 신고"</string>
     <string name="bugreport_option_full_summary" msgid="1975130009258435885">"기기가 응답하지 않거나 너무 느리거나 모든 보고서 섹션이 필요한 경우 이 옵션을 사용하여 시스템 방해를 최소화합니다. 세부정보를 추가하거나 스크린샷을 추가로 찍을 수 없습니다."</string>
     <string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{버그 신고 스크린샷을 #초 후에 찍습니다.}other{버그 신고 스크린샷을 #초 후에 찍습니다.}}"</string>
     <string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"버그 신고용 스크린샷 촬영 완료"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 688d6b2..13487f7 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1958,7 +1958,7 @@
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Šī lietotne pieprasa papildu drošību. Mēģiniet tai piekļūt savā tālrunī."</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Šajā ierīcē (<xliff:g id="DEVICE">%1$s</xliff:g>) nevar piekļūt tālvadībai. Mēģiniet tai piekļūt savā Android TV ierīcē."</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Šajā ierīcē (<xliff:g id="DEVICE">%1$s</xliff:g>) nevar piekļūt tālvadībai. Mēģiniet tai piekļūt savā planšetdatorā."</string>
-    <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Šajā ierīcē (<xliff:g id="DEVICE">%1$s</xliff:g>) nevar piekļūt tālvadībai. Mēģiniet tai piekļūt savā tālrunī."</string>
+    <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Ierīcē <xliff:g id="DEVICE">%1$s</xliff:g> nevar piekļūt šai funkcijai. Mēģiniet tai piekļūt tālrunī."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Šī lietotne tika izstrādāta vecākai Android versijai un var nedarboties pareizi. Meklējiet atjauninājumus vai sazinieties ar izstrādātāju."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Meklēt atjauninājumu"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Jums ir jaunas īsziņas."</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index abc5867..eab1b3e 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -124,7 +124,7 @@
     <string name="roamingTextSearching" msgid="5323235489657753486">"Leter etter tjeneste"</string>
     <string name="wfcRegErrorTitle" msgid="3193072971584858020">"Kunne ikke konfigurere wifi-anrop"</string>
   <string-array name="wfcOperatorErrorAlertMessages">
-    <item msgid="468830943567116703">"For å ringe og sende meldinger over Wi-Fi, må du først be operatøren om å konfigurere denne tjenesten. Deretter slår du på wifi-anrop igjen fra Innstillinger. (Feilkode: <xliff:g id="CODE">%1$s</xliff:g>)"</item>
+    <item msgid="468830943567116703">"For å ringe og sende meldinger over Wifi, må du først be operatøren om å konfigurere denne tjenesten. Deretter slår du på wifi-anrop igjen fra Innstillinger. (Feilkode: <xliff:g id="CODE">%1$s</xliff:g>)"</item>
   </string-array>
   <string-array name="wfcOperatorErrorNotificationMessages">
     <item msgid="4795145070505729156">"Problem med å registrere wifi-anrop med operatøren din: <xliff:g id="CODE">%1$s</xliff:g>"</item>
@@ -135,7 +135,7 @@
     <string name="wfcSpnFormat_spn_wifi_calling_vo_hyphen" msgid="3836827895369365298">"<xliff:g id="SPN">%s</xliff:g>-Wifi-anrop"</string>
     <string name="wfcSpnFormat_wlan_call" msgid="4895315549916165700">"WLAN-anrop"</string>
     <string name="wfcSpnFormat_spn_wlan_call" msgid="255919245825481510">"<xliff:g id="SPN">%s</xliff:g> WLAN-anrop"</string>
-    <string name="wfcSpnFormat_spn_wifi" msgid="7232899594327126970">"<xliff:g id="SPN">%s</xliff:g> Wi-Fi"</string>
+    <string name="wfcSpnFormat_spn_wifi" msgid="7232899594327126970">"<xliff:g id="SPN">%s</xliff:g> Wifi"</string>
     <string name="wfcSpnFormat_wifi_calling_bar_spn" msgid="8383917598312067365">"Wifi-anrop | <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="wfcSpnFormat_spn_vowifi" msgid="6865214948822061486">"<xliff:g id="SPN">%s</xliff:g> VoWifi"</string>
     <string name="wfcSpnFormat_wifi_calling" msgid="6178935388378661755">"Wifi-anrop"</string>
@@ -143,9 +143,9 @@
     <string name="wfcSpnFormat_wifi_calling_wo_hyphen" msgid="7178561009225028264">"Wifi-anrop"</string>
     <string name="wfcSpnFormat_vowifi" msgid="8371335230890725606">"VoWifi"</string>
     <string name="wifi_calling_off_summary" msgid="5626710010766902560">"Av"</string>
-    <string name="wfc_mode_wifi_preferred_summary" msgid="1035175836270943089">"Ring via Wi-Fi"</string>
+    <string name="wfc_mode_wifi_preferred_summary" msgid="1035175836270943089">"Ring via Wifi"</string>
     <string name="wfc_mode_cellular_preferred_summary" msgid="4958965609212575619">"Ring over mobilnettverk"</string>
-    <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Bare Wi-Fi"</string>
+    <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Bare Wifi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
     <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>-reserve for anrop"</string>
@@ -516,14 +516,14 @@
     <string name="permdesc_changeNetworkState" msgid="649341947816898736">"Lar appen endre innstillingene for nettverkstilknytning."</string>
     <string name="permlab_changeTetherState" msgid="9079611809931863861">"endre tilknytningsoppsett"</string>
     <string name="permdesc_changeTetherState" msgid="3025129606422533085">"Lar appen endre innstillingene for delt nettforbindelse."</string>
-    <string name="permlab_accessWifiState" msgid="5552488500317911052">"se Wi-Fi-tilkoblinger"</string>
-    <string name="permdesc_accessWifiState" msgid="6913641669259483363">"Lar appen se informasjon om Wi-Fi-nettverk, f.eks. hvorvidt Wi-Fi er aktivert og navn på tilkoblede Wi-Fi-enheter."</string>
-    <string name="permlab_changeWifiState" msgid="7947824109713181554">"koble til og fra Wi-Fi"</string>
-    <string name="permdesc_changeWifiState" msgid="7170350070554505384">"Lar appen koble til og fra Wi-Fi-tilgangspunkter, og å gjøre endringer i enhetens konfigurasjon for Wi-Fi-nettverk."</string>
+    <string name="permlab_accessWifiState" msgid="5552488500317911052">"se Wifi-tilkoblinger"</string>
+    <string name="permdesc_accessWifiState" msgid="6913641669259483363">"Lar appen se informasjon om Wifi-nettverk, f.eks. hvorvidt Wifi er aktivert og navn på tilkoblede Wifi-enheter."</string>
+    <string name="permlab_changeWifiState" msgid="7947824109713181554">"koble til og fra wifi"</string>
+    <string name="permdesc_changeWifiState" msgid="7170350070554505384">"Lar appen koble til og fra wifi-tilgangspunkter, og å gjøre endringer i enhetens konfigurasjon for wifi-nettverk."</string>
     <string name="permlab_changeWifiMulticastState" msgid="285626875870754696">"tillate multicast for trådløse nettverk"</string>
-    <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"Lar appen motta pakker som sendes til alle enhetene på et Wi-Fi-nettverk ved hjelp av multikastingsadresser,  Dette bruker mer strøm enn modusen uten multikasting."</string>
-    <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"Lar appen motta pakker som sendes til alle enhetene på et Wi-Fi-nettverk ved hjelp av multikastingsadresser, ikke bare Android TV-enheten din. Dette bruker mer strøm enn modus uten multikasting."</string>
-    <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"Lar appen motta pakker som sendes til alle enhetene på et Wi-Fi-nettverk ved hjelp av multikastingsadresser,  Dette bruker mer strøm enn modusen uten multikasting."</string>
+    <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"Lar appen motta pakker som sendes til alle enhetene på et Wifi-nettverk ved hjelp av multikastingsadresser,  Dette bruker mer strøm enn modusen uten multikasting."</string>
+    <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"Lar appen motta pakker som sendes til alle enhetene på et Wifi-nettverk ved hjelp av multikastingsadresser, ikke bare Android TV-enheten din. Dette bruker mer strøm enn modus uten multikasting."</string>
+    <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"Lar appen motta pakker som sendes til alle enhetene på et Wifi-nettverk ved hjelp av multikastingsadresser,  Dette bruker mer strøm enn modusen uten multikasting."</string>
     <string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"endre Bluetooth-innstillinger"</string>
     <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"Lar appen konfigurere det lokale Bluetooth-nettbrettet, samt oppdage og koble sammen med eksterne enheter."</string>
     <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"Lar appen konfigurere Bluetooth på Android TV-enheten din samt oppdage og koble sammen med eksterne enheter."</string>
@@ -546,8 +546,8 @@
     <string name="permdesc_bluetooth_advertise" product="default" msgid="6085174451034210183">"Lar appen vise annonser til Bluetooth-enheter i nærheten"</string>
     <string name="permlab_uwb_ranging" msgid="8141915781475770665">"fastslå relativ posisjon mellom enheter som bruker ultrabredbånd"</string>
     <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"tillate at appen fastslår den relative posisjonen mellom enheter i nærheten som bruker ultrabredbånd"</string>
-    <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"samhandle med Wi-Fi-enheter i nærheten"</string>
-    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Lar appen annonsere, koble til og fastslå den relative posisjonen til Wi-Fi-enheter i nærheten"</string>
+    <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"samhandle med wifi-enheter i nærheten"</string>
+    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Lar appen annonsere, koble til og fastslå den relative posisjonen til wifi-enheter i nærheten"</string>
     <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"Informasjon om prioritert NFC-betalingstjeneste"</string>
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Gir appen tilgang til informasjon om prioritert NFC-betalingstjeneste, for eksempel registrerte hjelpemidler og destinasjon."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"kontroller overføring av data med NFC-teknologi"</string>
@@ -1293,7 +1293,7 @@
     <string name="ringtone_picker_title_alarm" msgid="7438934548339024767">"Alarmlyder"</string>
     <string name="ringtone_picker_title_notification" msgid="6387191794719608122">"Varsellyder"</string>
     <string name="ringtone_unknown" msgid="5059495249862816475">"Ukjent"</string>
-    <string name="wifi_available_sign_in" msgid="381054692557675237">"Logg på Wi-Fi-nettverket"</string>
+    <string name="wifi_available_sign_in" msgid="381054692557675237">"Logg på Wifi-nettverket"</string>
     <string name="network_available_sign_in" msgid="1520342291829283114">"Logg på nettverk"</string>
     <!-- no translation found for network_available_sign_in_detailed (7520423801613396556) -->
     <skip />
@@ -1576,10 +1576,10 @@
     <string name="data_usage_warning_title" msgid="9034893717078325845">"Varsel om databruk"</string>
     <string name="data_usage_warning_body" msgid="1669325367188029454">"Du har brukt <xliff:g id="APP">%s</xliff:g> med data"</string>
     <string name="data_usage_mobile_limit_title" msgid="3911447354393775241">"Grensen for mobildata er nådd"</string>
-    <string name="data_usage_wifi_limit_title" msgid="2069698056520812232">"Datagrensen for Wi-Fi er nådd"</string>
+    <string name="data_usage_wifi_limit_title" msgid="2069698056520812232">"Datagrensen for wifi er nådd"</string>
     <string name="data_usage_limit_body" msgid="3567699582000085710">"Data er på pause i resten av syklusen"</string>
     <string name="data_usage_mobile_limit_snoozed_title" msgid="101888478915677895">"Over grensen for mobildata"</string>
-    <string name="data_usage_wifi_limit_snoozed_title" msgid="1622359254521960508">"Over grensen din for Wi-Fi-data"</string>
+    <string name="data_usage_wifi_limit_snoozed_title" msgid="1622359254521960508">"Over grensen din for wifi-data"</string>
     <string name="data_usage_limit_snoozed_body" msgid="545146591766765678">"Du er <xliff:g id="SIZE">%s</xliff:g> over den angitte grensen din"</string>
     <string name="data_usage_restricted_title" msgid="126711424380051268">"Bakgrunnsdata er begrenset"</string>
     <string name="data_usage_restricted_body" msgid="5338694433686077733">"Trykk for å fjerne begrensningen."</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index c1c0d54..73a5978 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -296,7 +296,7 @@
     <string name="foreground_service_multiple_separator" msgid="5002287361849863168">"<xliff:g id="LEFT_SIDE">%1$s</xliff:g>, <xliff:g id="RIGHT_SIDE">%2$s</xliff:g>"</string>
     <string name="safeMode" msgid="8974401416068943888">"Modo de segurança"</string>
     <string name="android_system_label" msgid="5974767339591067210">"Sistema Android"</string>
-    <string name="user_owner_label" msgid="8628726904184471211">"Deslize até o perfil pessoal"</string>
+    <string name="user_owner_label" msgid="8628726904184471211">"Mudar para o perfil pessoal"</string>
     <string name="managed_profile_label" msgid="7316778766973512382">"Perfil de trabalho"</string>
     <string name="permgrouplab_contacts" msgid="4254143639307316920">"Contatos"</string>
     <string name="permgroupdesc_contacts" msgid="9163927941244182567">"acesse seus contatos"</string>
@@ -749,7 +749,7 @@
     <string name="permdesc_bindCarrierServices" msgid="9185614481967262900">"Permite que o proprietário use serviços da operadora. Não deve ser necessário para apps comuns."</string>
     <string name="permlab_access_notification_policy" msgid="5524112842876975537">"acessar \"Não perturbe\""</string>
     <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permitir que o app leia e grave a configuração \"Não perturbe\"."</string>
-    <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"iniciar uso da permissão para visualização"</string>
+    <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"começar a usar a permissão para ver"</string>
     <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permite que o sistema inicie o uso de permissão para um app. Não deve ser necessário para apps comuns."</string>
     <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"decisões de permissão da visualização inicial"</string>
     <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Autoriza o detentor a iniciar a tela para revisar as decisões de permissão. Não deve ser necessário para apps normais."</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 1fe6e1c..0efbc73 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -164,7 +164,7 @@
     <string name="httpErrorAuth" msgid="469553140922938968">"Não foi possível autenticar."</string>
     <string name="httpErrorProxyAuth" msgid="7229662162030113406">"A autenticação através do servidor proxy falhou."</string>
     <string name="httpErrorConnect" msgid="3295081579893205617">"Não foi possível ligar ao servidor."</string>
-    <string name="httpErrorIO" msgid="3860318696166314490">"Não foi possível comunicar com o servidor. Tente novamente mais tarde."</string>
+    <string name="httpErrorIO" msgid="3860318696166314490">"Não foi possível comunicar com o servidor. Tente mais tarde."</string>
     <string name="httpErrorTimeout" msgid="7446272815190334204">"Esgotou o tempo limite da ligação ao servidor."</string>
     <string name="httpErrorRedirectLoop" msgid="8455757777509512098">"A página contém demasiados redireccionamentos do servidor."</string>
     <string name="httpErrorUnsupportedScheme" msgid="2664108769858966374">"O protocolo não é suportado."</string>
@@ -172,7 +172,7 @@
     <string name="httpErrorBadUrl" msgid="754447723314832538">"Não foi possível abrir a página porque o URL é inválido."</string>
     <string name="httpErrorFile" msgid="3400658466057744084">"Não foi possível aceder ao ficheiro."</string>
     <string name="httpErrorFileNotFound" msgid="5191433324871147386">"Não foi possível localizar o ficheiro solicitado."</string>
-    <string name="httpErrorTooManyRequests" msgid="2149677715552037198">"Existem demasiados pedidos em processamento. Tente novamente mais tarde."</string>
+    <string name="httpErrorTooManyRequests" msgid="2149677715552037198">"Existem demasiados pedidos em processamento. Tente mais tarde."</string>
     <string name="notification_title" msgid="5783748077084481121">"Erro de início de sessão de <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string>
     <string name="contentServiceSync" msgid="2341041749565687871">"Sincronização"</string>
     <string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"Não é possível sincronizar"</string>
@@ -671,7 +671,7 @@
     <string name="face_error_no_space" msgid="5649264057026021723">"Não pode guardar novos dados de rostos. Elimine um antigo."</string>
     <string name="face_error_canceled" msgid="2164434737103802131">"Operação de rosto cancelada."</string>
     <string name="face_error_user_canceled" msgid="5766472033202928373">"Desbloqueio facial cancelado pelo utilizador"</string>
-    <string name="face_error_lockout" msgid="7864408714994529437">"Demasiadas tentativas. Tente novamente mais tarde."</string>
+    <string name="face_error_lockout" msgid="7864408714994529437">"Demasiadas tentativas. Tente mais tarde."</string>
     <string name="face_error_lockout_permanent" msgid="3277134834042995260">"Demasiadas tentativas. O Desbloqueio facial foi desativado."</string>
     <string name="face_error_lockout_screen_lock" msgid="5062609811636860928">"Demasiadas tentativas. Em alternativa, introduza o bloqueio de ecrã."</string>
     <string name="face_error_unable_to_process" msgid="5723292697366130070">"Não é possível validar o rosto. Tente novamente."</string>
@@ -1836,7 +1836,7 @@
     <string name="restr_pin_create_pin" msgid="917067613896366033">"Crie um PIN para modificar as restrições"</string>
     <string name="restr_pin_error_doesnt_match" msgid="7063392698489280556">"Os PINs não correspondem. Tente novamente."</string>
     <string name="restr_pin_error_too_short" msgid="1547007808237941065">"O PIN é demasiado pequeno. Deve ter, no mínimo, 4 dígitos."</string>
-    <string name="restr_pin_try_later" msgid="5897719962541636727">"Tente novamente mais tarde"</string>
+    <string name="restr_pin_try_later" msgid="5897719962541636727">"Tente mais tarde"</string>
     <string name="immersive_cling_title" msgid="2307034298721541791">"Visualização de ecrã inteiro"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Para sair, deslize rapidamente para baixo a partir da parte superior."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index c1c0d54..73a5978 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -296,7 +296,7 @@
     <string name="foreground_service_multiple_separator" msgid="5002287361849863168">"<xliff:g id="LEFT_SIDE">%1$s</xliff:g>, <xliff:g id="RIGHT_SIDE">%2$s</xliff:g>"</string>
     <string name="safeMode" msgid="8974401416068943888">"Modo de segurança"</string>
     <string name="android_system_label" msgid="5974767339591067210">"Sistema Android"</string>
-    <string name="user_owner_label" msgid="8628726904184471211">"Deslize até o perfil pessoal"</string>
+    <string name="user_owner_label" msgid="8628726904184471211">"Mudar para o perfil pessoal"</string>
     <string name="managed_profile_label" msgid="7316778766973512382">"Perfil de trabalho"</string>
     <string name="permgrouplab_contacts" msgid="4254143639307316920">"Contatos"</string>
     <string name="permgroupdesc_contacts" msgid="9163927941244182567">"acesse seus contatos"</string>
@@ -749,7 +749,7 @@
     <string name="permdesc_bindCarrierServices" msgid="9185614481967262900">"Permite que o proprietário use serviços da operadora. Não deve ser necessário para apps comuns."</string>
     <string name="permlab_access_notification_policy" msgid="5524112842876975537">"acessar \"Não perturbe\""</string>
     <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permitir que o app leia e grave a configuração \"Não perturbe\"."</string>
-    <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"iniciar uso da permissão para visualização"</string>
+    <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"começar a usar a permissão para ver"</string>
     <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permite que o sistema inicie o uso de permissão para um app. Não deve ser necessário para apps comuns."</string>
     <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"decisões de permissão da visualização inicial"</string>
     <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Autoriza o detentor a iniciar a tela para revisar as decisões de permissão. Não deve ser necessário para apps normais."</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 86115d5..c2fe6dd 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1953,13 +1953,13 @@
     <string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"Настройки телефона недоступны"</string>
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Эта функция пока недоступна на устройстве <xliff:g id="DEVICE">%1$s</xliff:g>. Используйте Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Эта функция пока недоступна на устройстве <xliff:g id="DEVICE">%1$s</xliff:g>. Используйте планшет."</string>
-    <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Эта функция пока недоступна на устройстве <xliff:g id="DEVICE">%1$s</xliff:g>. Используйте телефон."</string>
+    <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"На устройстве <xliff:g id="DEVICE">%1$s</xliff:g> эта функция пока недоступна. Используйте телефон."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Это приложение запрашивает дополнительные меры защиты. Используйте Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Это приложение запрашивает дополнительные меры защиты. Используйте планшет."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Это приложение запрашивает дополнительные меры защиты. Используйте телефон."</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Эта функция недоступна на устройстве <xliff:g id="DEVICE">%1$s</xliff:g>. Используйте Android TV."</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Эта функция недоступна на устройстве <xliff:g id="DEVICE">%1$s</xliff:g>. Используйте планшет."</string>
-    <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Эта функция недоступна на устройстве <xliff:g id="DEVICE">%1$s</xliff:g>. Используйте телефон."</string>
+    <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"На устройстве <xliff:g id="DEVICE">%1$s</xliff:g> эта функция недоступна. Используйте телефон."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Это приложение было создано для более ранней версии Android и может работать со сбоями. Проверьте наличие обновлений или свяжитесь с разработчиком."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Проверить обновления"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Новые сообщения"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 131ccd0..46a5ca4 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -499,7 +499,7 @@
     <string name="permlab_setWallpaper" msgid="6959514622698794511">"వాల్‌పేపర్‌ను సెట్ చేయడం"</string>
     <string name="permdesc_setWallpaper" msgid="2973996714129021397">"సిస్టమ్ వాల్‌పేపర్‌ను సెట్ చేయడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_setWallpaperHints" msgid="1153485176642032714">"మీ వాల్‌పేపర్ పరిమాణాన్ని సర్దుబాటు చేయడం"</string>
-    <string name="permdesc_setWallpaperHints" msgid="6257053376990044668">"సిస్టమ్ వాల్‌పేపర్ పరిమాణం సూచనలను సెట్ చేయడానికి యాప్‌ను అనుమతిస్తుంది."</string>
+    <string name="permdesc_setWallpaperHints" msgid="6257053376990044668">"సిస్టమ్ వాల్‌పేపర్ సైజ్‌ సూచనలను సెట్ చేయడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_setTimeZone" msgid="7922618798611542432">"సమయ మండలిని సెట్ చేయడం"</string>
     <string name="permdesc_setTimeZone" product="tablet" msgid="1788868809638682503">"టాబ్లెట్ యొక్క సమయ మండలిని మార్చడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permdesc_setTimeZone" product="tv" msgid="9069045914174455938">"మీ Android TV పరికరం సమయ మండలిని మార్చడానికి యాప్‌ని అనుమతిస్తుంది."</string>
@@ -560,7 +560,7 @@
     <string name="permdesc_postNotification" msgid="5974977162462877075">"నోటిఫికేషన్‌లను చూపించడానికి యాప్‌ను అనుమతించండి"</string>
     <string name="permlab_useBiometric" msgid="6314741124749633786">"బయోమెట్రిక్ హార్డ్‌వేర్‌ని ఉపయోగించు"</string>
     <string name="permdesc_useBiometric" msgid="7502858732677143410">"ప్రమాణీకరణ కోసం బయోమెట్రిక్ హార్డ్‌వేర్‌ను ఉపయోగించడానికి యాప్‌ని అనుమతిస్తుంది"</string>
-    <string name="permlab_manageFingerprint" msgid="7432667156322821178">"వేలిముద్ర హార్డ్‌వేర్‌ని నిర్వహించడానికి అనుమతి"</string>
+    <string name="permlab_manageFingerprint" msgid="7432667156322821178">"వేలిముద్ర హార్డ్‌వేర్‌ని మేనేజ్ చేయడానికి అనుమతి"</string>
     <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"వినియోగం కోసం వేలిముద్ర టెంప్లేట్‌లను జోడించే, తొలగించే పద్ధతులను అమలు చేయడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_useFingerprint" msgid="1001421069766751922">"వేలిముద్ర హార్డ్‌వేర్‌ని ఉపయోగించడానికి అనుమతి"</string>
     <string name="permdesc_useFingerprint" msgid="412463055059323742">"ప్రామాణీకరణ కోసం వేలిముద్ర హార్డ్‌వేర్‌ను ఉపయోగించడానికి యాప్‌ను అనుమతిస్తుంది"</string>
@@ -709,7 +709,7 @@
     <string name="permlab_register_call_provider" msgid="6135073566140050702">"కొత్త టెలికామ్ కనెక్షన్‌లను నమోదు చేయడం"</string>
     <string name="permdesc_register_call_provider" msgid="4201429251459068613">"కొత్త టెలికామ్ కనెక్షన్‌లను నమోదు చేయడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_connection_manager" msgid="3179365584691166915">"టెలికామ్ కనెక్షన్‌లను నిర్వహించడం"</string>
-    <string name="permdesc_connection_manager" msgid="1426093604238937733">"టెలికామ్ కనెక్షన్‌లను నిర్వహించడానికి యాప్‌ను అనుమతిస్తుంది."</string>
+    <string name="permdesc_connection_manager" msgid="1426093604238937733">"టెలికామ్ కనెక్షన్‌లను మేనేజ్ చేయడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_bind_incall_service" msgid="5990625112603493016">"ఇన్-కాల్ స్క్రీన్‌తో పరస్పర చర్య చేయడం"</string>
     <string name="permdesc_bind_incall_service" msgid="4124917526967765162">"వినియోగదారునికి ఇన్-కాల్ స్క్రీన్ ఎప్పుడు, ఎలా కనిపించాలనే దాన్ని నియంత్రించడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_bind_connection_service" msgid="5409268245525024736">"టెలిఫోన్ సేవలతో పరస్పర చర్య చేయడం"</string>
@@ -719,7 +719,7 @@
     <string name="permlab_readNetworkUsageHistory" msgid="8470402862501573795">"చారిత్రక నెట్‌వర్క్ వినియోగాన్ని చదవడం"</string>
     <string name="permdesc_readNetworkUsageHistory" msgid="1112962304941637102">"నిర్దిష్ట నెట్‌వర్క్‌లు మరియు యాప్‌ల కోసం చారిత్రాత్మక నెట్‌వర్క్ వినియోగాన్ని చదవడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_manageNetworkPolicy" msgid="6872549423152175378">"నెట్‌వర్క్ విధానాన్ని నిర్వహించడం"</string>
-    <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"నెట్‌వర్క్ విధానాలను నిర్వహించడానికి మరియు యాప్-నిర్దిష్ట నిబంధనలను నిర్వచించడానికి యాప్‌ను అనుమతిస్తుంది."</string>
+    <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"నెట్‌వర్క్ విధానాలను మేనేజ్ చేయడానికి మరియు యాప్-నిర్దిష్ట నిబంధనలను నిర్వచించడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_modifyNetworkAccounting" msgid="7448790834938749041">"నెట్‌వర్క్ వినియోగ అకౌంటింగ్‌ను ఎడిట్ చేయడం"</string>
     <string name="permdesc_modifyNetworkAccounting" msgid="5076042642247205390">"యాప్‌లలో నెట్‌వర్క్ వినియోగం ఎలా గణించాలనే దాన్ని ఎడిట్ చేయడానికి యాప్‌ను అనుమతిస్తుంది. సాధారణ యాప్‌ల ద్వారా ఉపయోగించడానికి ఉద్దేశించినది కాదు."</string>
     <string name="permlab_accessNotifications" msgid="7130360248191984741">"నోటిఫికేషన్‌లను యాక్సెస్ చేయడం"</string>
@@ -1446,7 +1446,7 @@
     <string name="ext_media_status_ejecting" msgid="7532403368044013797">"తొలగిస్తోంది…"</string>
     <string name="ext_media_status_formatting" msgid="774148701503179906">"ఫార్మాట్ చేస్తోంది..."</string>
     <string name="ext_media_status_missing" msgid="6520746443048867314">"చొప్పించబడలేదు"</string>
-    <string name="activity_list_empty" msgid="4219430010716034252">"సరిపోలే కార్యాచరణలు కనుగొనబడలేదు."</string>
+    <string name="activity_list_empty" msgid="4219430010716034252">"మ్యాచ్ అయ్యే కార్యాచరణలు కనుగొనబడలేదు."</string>
     <string name="permlab_route_media_output" msgid="8048124531439513118">"మీడియా అవుట్‌పుట్‌ను మళ్లించడం"</string>
     <string name="permdesc_route_media_output" msgid="1759683269387729675">"మీడియా అవుట్‌పుట్‌ను ఇతర బాహ్య పరికరాలకు మళ్లించడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_readInstallSessions" msgid="7279049337895583621">"ఇన్‌స్టాల్ సెషన్‌లను చదవడం"</string>
@@ -1491,8 +1491,8 @@
     <string name="notification_ranker_binding_label" msgid="432708245635563763">"నోటిఫికేషన్ ర్యాంకర్ సేవ"</string>
     <string name="vpn_title" msgid="5906991595291514182">"VPN సక్రియం చేయబడింది"</string>
     <string name="vpn_title_long" msgid="6834144390504619998">"<xliff:g id="APP">%s</xliff:g> ద్వారా VPN సక్రియం చేయబడింది"</string>
-    <string name="vpn_text" msgid="2275388920267251078">"నెట్‌వర్క్‌ను నిర్వహించడానికి నొక్కండి."</string>
-    <string name="vpn_text_long" msgid="278540576806169831">"<xliff:g id="SESSION">%s</xliff:g>కు కనెక్ట్ చేయబడింది. నెట్‌వర్క్‌ను నిర్వహించడానికి నొక్కండి."</string>
+    <string name="vpn_text" msgid="2275388920267251078">"నెట్‌వర్క్‌ను మేనేజ్ చేయడానికి నొక్కండి."</string>
+    <string name="vpn_text_long" msgid="278540576806169831">"<xliff:g id="SESSION">%s</xliff:g>కు కనెక్ట్ చేయబడింది. నెట్‌వర్క్‌ను మేనేజ్ చేయడానికి నొక్కండి."</string>
     <string name="vpn_lockdown_connecting" msgid="6096725311950342607">"ఎల్లప్పుడూ-ఆన్‌లో ఉండే VPN కనెక్ట్ చేయబడుతోంది…"</string>
     <string name="vpn_lockdown_connected" msgid="2853127976590658469">"ఎల్లప్పుడూ-ఆన్‌లో ఉండే VPN కనెక్ట్ చేయబడింది"</string>
     <string name="vpn_lockdown_disconnected" msgid="5573611651300764955">"ఎల్లప్పుడూ ఆన్‌లో ఉండే VPN నుండి డిస్‌కనెక్ట్ చేయబడింది"</string>
@@ -1723,7 +1723,7 @@
     <string name="guest_name" msgid="8502103277839834324">"గెస్ట్"</string>
     <string name="error_message_title" msgid="4082495589294631966">"ఎర్రర్"</string>
     <string name="error_message_change_not_allowed" msgid="843159705042381454">"ఈ మార్పును మీ నిర్వాహకులు అనుమతించలేదు"</string>
-    <string name="app_not_found" msgid="3429506115332341800">"ఈ చర్యను నిర్వహించడానికి యాప్ ఏదీ కనుగొనబడలేదు"</string>
+    <string name="app_not_found" msgid="3429506115332341800">"ఈ చర్యను మేనేజ్ చేయడానికి యాప్ ఏదీ కనుగొనబడలేదు"</string>
     <string name="revoke" msgid="5526857743819590458">"ఉపసంహరించండి"</string>
     <string name="mediasize_iso_a0" msgid="7039061159929977973">"ISO A0"</string>
     <string name="mediasize_iso_a1" msgid="4063589931031977223">"ISO A1"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 7bb0ba9..ea26a15 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -988,7 +988,7 @@
     <string name="keyguard_accessibility_unlock_area_collapsed" msgid="4729922043778400434">"Pinaliit ang bahagi ng pag-unlock."</string>
     <string name="keyguard_accessibility_widget" msgid="6776892679715699875">"<xliff:g id="WIDGET_INDEX">%1$s</xliff:g> widget."</string>
     <string name="keyguard_accessibility_user_selector" msgid="1466067610235696600">"Tagapili ng user"</string>
-    <string name="keyguard_accessibility_status" msgid="6792745049712397237">"Katayuan"</string>
+    <string name="keyguard_accessibility_status" msgid="6792745049712397237">"Status"</string>
     <string name="keyguard_accessibility_camera" msgid="7862557559464986528">"Camera"</string>
     <string name="keygaurd_accessibility_media_controls" msgid="2267379779900620614">"Mga kontrol ng media"</string>
     <string name="keyguard_accessibility_widget_reorder_start" msgid="7066213328912939191">"Nagsimula na ang pagbabago ng ayos ng widget."</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index d46bdc8..d0d585a 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1562,7 +1562,7 @@
     <string name="content_description_sliding_handle" msgid="982510275422590757">"滑动手柄。触摸并按住。"</string>
     <string name="description_target_unlock_tablet" msgid="7431571180065859551">"滑动解锁。"</string>
     <string name="action_bar_home_description" msgid="1501655419158631974">"导航首页"</string>
-    <string name="action_bar_up_description" msgid="6611579697195026932">"向上导航"</string>
+    <string name="action_bar_up_description" msgid="6611579697195026932">"返回"</string>
     <string name="action_menu_overflow_description" msgid="4579536843510088170">"更多选项"</string>
     <string name="action_bar_home_description_format" msgid="5087107531331621803">"%1$s:%2$s"</string>
     <string name="action_bar_home_subtitle_description_format" msgid="4346835454749569826">"%1$s - %2$s:%3$s"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 43de5ba..8f72050 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1950,13 +1950,13 @@
     <string name="app_streaming_blocked_title_for_settings_dialog" product="tablet" msgid="8222710146267948647">"無法使用平板電腦設定"</string>
     <string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"無法使用手機設定"</string>
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用 Android TV 裝置。"</string>
-    <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用平板電腦。"</string>
+    <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個項目,請改用平板電腦。"</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用手機。"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"這個應用程式要求進行額外的安全性驗證,請改用 Android TV 裝置。"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"這個應用程式要求進行額外的安全性驗證,請改用平板電腦。"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"這個應用程式要求進行額外的安全性驗證,請改用手機。"</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用 Android TV 裝置。"</string>
-    <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用平板電腦。"</string>
+    <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個項目,請改用平板電腦。"</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用手機。"</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"這個應用程式是專為舊版 Android 所打造,因此可能無法正常運作。請嘗試檢查更新,或是與開發人員聯絡。"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"檢查更新"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8428060..881d499 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -701,7 +701,7 @@
 
     <!-- Indicates the time needed to time out the fold animation if the device stops in half folded
          mode. -->
-    <integer name="config_unfoldTransitionHalfFoldedTimeout">600</integer>
+    <integer name="config_unfoldTransitionHalfFoldedTimeout">1000</integer>
 
     <!-- Indicates that the device supports having more than one internal display on at the same
          time. Only applicable to devices with more than one internal display. If this option is
@@ -2479,6 +2479,13 @@
     <integer name="config_attentionMaximumExtension">900000</integer> <!-- 15 minutes.  -->
     <!-- Is the system user the only user allowed to dream. -->
     <bool name="config_dreamsOnlyEnabledForSystemUser">false</bool>
+    <!-- Whether dreams are disabled when ambient mode is suppressed. -->
+    <bool name="config_dreamsDisabledByAmbientModeSuppressionConfig">false</bool>
+
+    <!-- The duration in milliseconds of the dream opening animation.  -->
+    <integer name="config_dreamOpenAnimationDuration">250</integer>
+    <!-- The duration in milliseconds of the dream closing animation.  -->
+    <integer name="config_dreamCloseAnimationDuration">100</integer>
 
     <!-- Whether to dismiss the active dream when an activity is started. Doesn't apply to
          assistant activities (ACTIVITY_TYPE_ASSISTANT) -->
@@ -3575,9 +3582,9 @@
          config_sidefpsSkipWaitForPowerVendorAcquireMessage -->
     <integer name="config_sidefpsSkipWaitForPowerAcquireMessage">6</integer>
 
-    <!-- This vendor acquired message that will cause the sidefpsKgPowerPress window to be skipped.
-         config_sidefpsSkipWaitForPowerOnFingerUp must be true and
-         config_sidefpsSkipWaitForPowerAcquireMessage must be BIOMETRIC_ACQUIRED_VENDOR == 6. -->
+    <!-- This vendor acquired message will cause the sidefpsKgPowerPress window to be skipped
+         when config_sidefpsSkipWaitForPowerAcquireMessage == 6 (VENDOR) and the vendor acquire
+         message equals this constant -->
     <integer name="config_sidefpsSkipWaitForPowerVendorAcquireMessage">2</integer>
 
     <!-- This config is used to force VoiceInteractionService to start on certain low ram devices.
@@ -5956,4 +5963,8 @@
          TODO(b/236022708) Move rear display state to device state config file
     -->
     <integer name="config_deviceStateRearDisplay">-1</integer>
+
+    <!-- Whether the lock screen is allowed to run its own live wallpaper,
+         different from the home screen wallpaper. -->
+    <bool name="config_independentLockscreenLiveWallpaper">false</bool>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 97105d8..7a7b43a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2235,7 +2235,10 @@
   <java-symbol type="integer" name="config_dreamsBatteryLevelMinimumWhenNotPowered" />
   <java-symbol type="integer" name="config_dreamsBatteryLevelDrainCutoff" />
   <java-symbol type="string" name="config_dreamsDefaultComponent" />
+  <java-symbol type="bool" name="config_dreamsDisabledByAmbientModeSuppressionConfig" />
   <java-symbol type="bool" name="config_dreamsOnlyEnabledForSystemUser" />
+  <java-symbol type="integer" name="config_dreamOpenAnimationDuration" />
+  <java-symbol type="integer" name="config_dreamCloseAnimationDuration" />
   <java-symbol type="array" name="config_supportedDreamComplications" />
   <java-symbol type="array" name="config_disabledDreamComponents" />
   <java-symbol type="bool" name="config_dismissDreamOnActivityStart" />
@@ -4846,6 +4849,7 @@
   <java-symbol type="array" name="config_deviceStatesAvailableForAppRequests" />
   <java-symbol type="array" name="config_serviceStateLocationAllowedPackages" />
   <java-symbol type="integer" name="config_deviceStateRearDisplay"/>
+  <java-symbol type="bool" name="config_independentLockscreenLiveWallpaper"/>
 
   <!-- For app language picker -->
   <java-symbol type="string" name="system_locale_title" />
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 0b8b29b..bcb13d2 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -48,6 +48,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.fail;
 
 import static org.junit.Assert.assertEquals;
@@ -56,7 +57,9 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
 import android.app.Notification.CallStyle;
@@ -68,6 +71,7 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Color;
+import android.graphics.Typeface;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Build;
@@ -79,7 +83,9 @@
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
 import android.text.style.TextAppearanceSpan;
+import android.util.Pair;
 import android.widget.RemoteViews;
 
 import androidx.test.InstrumentationRegistry;
@@ -89,6 +95,8 @@
 import com.android.internal.R;
 import com.android.internal.util.ContrastColorUtil;
 
+import junit.framework.Assert;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -218,8 +226,10 @@
 
     @Test
     public void allPendingIntents_recollectedAfterReusingBuilder() {
-        PendingIntent intent1 = PendingIntent.getActivity(mContext, 0, new Intent("test1"), PendingIntent.FLAG_MUTABLE_UNAUDITED);
-        PendingIntent intent2 = PendingIntent.getActivity(mContext, 0, new Intent("test2"), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        PendingIntent intent1 = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
+        PendingIntent intent2 = PendingIntent.getActivity(
+                mContext, 0, new Intent("test2"), PendingIntent.FLAG_IMMUTABLE);
 
         Notification.Builder builder = new Notification.Builder(mContext, "channel");
         builder.setContentIntent(intent1);
@@ -669,30 +679,23 @@
         Notification notification = new Notification.Builder(mContext, "Channel").setStyle(
                 style).build();
 
+        int targetSize = mContext.getResources().getDimensionPixelSize(
+                ActivityManager.isLowRamDeviceStatic()
+                        ? R.dimen.notification_person_icon_max_size_low_ram
+                        : R.dimen.notification_person_icon_max_size);
+
         Bitmap personIcon = style.getUser().getIcon().getBitmap();
-        assertThat(personIcon.getWidth()).isEqualTo(
-                mContext.getResources().getDimensionPixelSize(
-                        R.dimen.notification_person_icon_max_size));
-        assertThat(personIcon.getHeight()).isEqualTo(
-                mContext.getResources().getDimensionPixelSize(
-                        R.dimen.notification_person_icon_max_size));
+        assertThat(personIcon.getWidth()).isEqualTo(targetSize);
+        assertThat(personIcon.getHeight()).isEqualTo(targetSize);
 
         Bitmap avatarIcon = style.getMessages().get(0).getSenderPerson().getIcon().getBitmap();
-        assertThat(avatarIcon.getWidth()).isEqualTo(
-                mContext.getResources().getDimensionPixelSize(
-                        R.dimen.notification_person_icon_max_size));
-        assertThat(avatarIcon.getHeight()).isEqualTo(
-                mContext.getResources().getDimensionPixelSize(
-                        R.dimen.notification_person_icon_max_size));
+        assertThat(avatarIcon.getWidth()).isEqualTo(targetSize);
+        assertThat(avatarIcon.getHeight()).isEqualTo(targetSize);
 
         Bitmap historicAvatarIcon = style.getHistoricMessages().get(
                 0).getSenderPerson().getIcon().getBitmap();
-        assertThat(historicAvatarIcon.getWidth()).isEqualTo(
-                mContext.getResources().getDimensionPixelSize(
-                        R.dimen.notification_person_icon_max_size));
-        assertThat(historicAvatarIcon.getHeight()).isEqualTo(
-                mContext.getResources().getDimensionPixelSize(
-                        R.dimen.notification_person_icon_max_size));
+        assertThat(historicAvatarIcon.getWidth()).isEqualTo(targetSize);
+        assertThat(historicAvatarIcon.getHeight()).isEqualTo(targetSize);
     }
 
     @Test
@@ -780,7 +783,6 @@
         assertFalse(notification.isMediaNotification());
     }
 
-    @Test
     public void validateColorizedPaletteForColor(int rawColor) {
         Notification.Colors cDay = new Notification.Colors();
         Notification.Colors cNight = new Notification.Colors();
@@ -861,19 +863,22 @@
         Bundle fakeTypes = new Bundle();
         fakeTypes.putParcelable(EXTRA_LARGE_ICON_BIG, new Bundle());
 
-        style.restoreFromExtras(fakeTypes);
 
         // no crash, good
     }
 
     @Test
     public void testRestoreFromExtras_Messaging_invalidExtra_noCrash() {
-        Notification.Style style = new Notification.MessagingStyle();
+        Notification.Style style = new Notification.MessagingStyle("test");
         Bundle fakeTypes = new Bundle();
         fakeTypes.putParcelable(EXTRA_MESSAGING_PERSON, new Bundle());
         fakeTypes.putParcelable(EXTRA_CONVERSATION_ICON, new Bundle());
 
-        style.restoreFromExtras(fakeTypes);
+        Notification n = new Notification.Builder(mContext, "test")
+                .setStyle(style)
+                .setExtras(fakeTypes)
+                .build();
+        Notification.Builder.recoverBuilder(mContext, n);
 
         // no crash, good
     }
@@ -885,22 +890,33 @@
         fakeTypes.putParcelable(EXTRA_MEDIA_SESSION, new Bundle());
         fakeTypes.putParcelable(EXTRA_MEDIA_REMOTE_INTENT, new Bundle());
 
-        style.restoreFromExtras(fakeTypes);
+        Notification n = new Notification.Builder(mContext, "test")
+                .setStyle(style)
+                .setExtras(fakeTypes)
+                .build();
+        Notification.Builder.recoverBuilder(mContext, n);
 
         // no crash, good
     }
 
     @Test
     public void testRestoreFromExtras_Call_invalidExtra_noCrash() {
-        Notification.Style style = new CallStyle();
+        PendingIntent intent1 = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
+        Notification.Style style = Notification.CallStyle.forIncomingCall(
+                new Person.Builder().setName("hi").build(), intent1, intent1);
+
         Bundle fakeTypes = new Bundle();
         fakeTypes.putParcelable(EXTRA_CALL_PERSON, new Bundle());
         fakeTypes.putParcelable(EXTRA_ANSWER_INTENT, new Bundle());
         fakeTypes.putParcelable(EXTRA_DECLINE_INTENT, new Bundle());
         fakeTypes.putParcelable(EXTRA_HANG_UP_INTENT, new Bundle());
 
-        style.restoreFromExtras(fakeTypes);
-
+        Notification n = new Notification.Builder(mContext, "test")
+                .setStyle(style)
+                .setExtras(fakeTypes)
+                .build();
+        Notification.Builder.recoverBuilder(mContext, n);
         // no crash, good
     }
 
@@ -962,7 +978,11 @@
         fakeTypes.putParcelable(KEY_ON_READ, new Bundle());
         fakeTypes.putParcelable(KEY_ON_REPLY, new Bundle());
         fakeTypes.putParcelable(KEY_REMOTE_INPUT, new Bundle());
-        Notification.CarExtender.UnreadConversation.getUnreadConversationFromBundle(fakeTypes);
+
+        Notification n = new Notification.Builder(mContext, "test")
+                .setExtras(fakeTypes)
+                .build();
+        Notification.CarExtender extender = new Notification.CarExtender(n);
 
         // no crash, good
     }
@@ -980,6 +1000,493 @@
         // no crash, good
     }
 
+
+    @Test
+    public void testDoesNotStripsExtenders() {
+        Notification.Builder nb = new Notification.Builder(mContext, "channel");
+        nb.extend(new Notification.CarExtender().setColor(Color.RED));
+        nb.extend(new Notification.TvExtender().setChannelId("different channel"));
+        nb.extend(new Notification.WearableExtender().setDismissalId("dismiss"));
+        Notification before = nb.build();
+        Notification after = Notification.Builder.maybeCloneStrippedForDelivery(before);
+
+        assertTrue(before == after);
+
+        Assert.assertEquals("different channel",
+                new Notification.TvExtender(before).getChannelId());
+        Assert.assertEquals(Color.RED, new Notification.CarExtender(before).getColor());
+        Assert.assertEquals("dismiss", new Notification.WearableExtender(before).getDismissalId());
+    }
+
+    @Test
+    public void testStyleChangeVisiblyDifferent_noStyles() {
+        Notification.Builder n1 = new Notification.Builder(mContext, "test");
+        Notification.Builder n2 = new Notification.Builder(mContext, "test");
+
+        assertFalse(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testStyleChangeVisiblyDifferent_noStyleToStyle() {
+        Notification.Builder n1 = new Notification.Builder(mContext, "test");
+        Notification.Builder n2 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.BigTextStyle());
+
+        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testStyleChangeVisiblyDifferent_styleToNoStyle() {
+        Notification.Builder n2 = new Notification.Builder(mContext, "test");
+        Notification.Builder n1 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.BigTextStyle());
+
+        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testStyleChangeVisiblyDifferent_changeStyle() {
+        Notification.Builder n1 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.InboxStyle());
+        Notification.Builder n2 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.BigTextStyle());
+
+        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testInboxTextChange() {
+        Notification.Builder nInbox1 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.InboxStyle().addLine("a").addLine("b"));
+        Notification.Builder nInbox2 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.InboxStyle().addLine("b").addLine("c"));
+
+        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nInbox1, nInbox2));
+    }
+
+    @Test
+    public void testBigTextTextChange() {
+        Notification.Builder nBigText1 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.BigTextStyle().bigText("something"));
+        Notification.Builder nBigText2 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.BigTextStyle().bigText("else"));
+
+        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigText1, nBigText2));
+    }
+
+    @Test
+    public void testBigPictureChange() {
+        Bitmap bitA = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
+        Bitmap bitB = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
+
+        Notification.Builder nBigPic1 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.BigPictureStyle().bigPicture(bitA));
+        Notification.Builder nBigPic2 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.BigPictureStyle().bigPicture(bitB));
+
+        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigPic1, nBigPic2));
+    }
+
+    @Test
+    public void testMessagingChange_text() {
+        Notification.Builder nM1 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(new Notification.MessagingStyle.Message(
+                                "a", 100, new Person.Builder().setName("hi").build())));
+        Notification.Builder nM2 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(new Notification.MessagingStyle.Message(
+                                "a", 100, new Person.Builder().setName("hi").build()))
+                        .addMessage(new Notification.MessagingStyle.Message(
+                                "b", 100, new Person.Builder().setName("hi").build()))
+                );
+
+        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
+    }
+
+    @Test
+    public void testMessagingChange_data() {
+        Notification.Builder nM1 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(new Notification.MessagingStyle.Message(
+                                "a", 100, new Person.Builder().setName("hi").build())
+                                .setData("text", mock(Uri.class))));
+        Notification.Builder nM2 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(new Notification.MessagingStyle.Message(
+                                "a", 100, new Person.Builder().setName("hi").build())));
+
+        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
+    }
+
+    @Test
+    public void testMessagingChange_sender() {
+        Person a = new Person.Builder().setName("A").build();
+        Person b = new Person.Builder().setName("b").build();
+        Notification.Builder nM1 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(new Notification.MessagingStyle.Message("a", 100, b)));
+        Notification.Builder nM2 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(new Notification.MessagingStyle.Message("a", 100, a)));
+
+        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
+    }
+
+    @Test
+    public void testMessagingChange_key() {
+        Person a = new Person.Builder().setName("hi").setKey("A").build();
+        Person b = new Person.Builder().setName("hi").setKey("b").build();
+        Notification.Builder nM1 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(new Notification.MessagingStyle.Message("a", 100, a)));
+        Notification.Builder nM2 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(new Notification.MessagingStyle.Message("a", 100, b)));
+
+        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
+    }
+
+    @Test
+    public void testMessagingChange_ignoreTimeChange() {
+        Notification.Builder nM1 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(new Notification.MessagingStyle.Message(
+                                "a", 100, new Person.Builder().setName("hi").build())));
+        Notification.Builder nM2 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(new Notification.MessagingStyle.Message(
+                                "a", 1000, new Person.Builder().setName("hi").build()))
+                );
+
+        assertFalse(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
+    }
+
+    @Test
+    public void testRemoteViews_nullChange() {
+        Notification.Builder n1 = new Notification.Builder(mContext, "test")
+                .setContent(mock(RemoteViews.class));
+        Notification.Builder n2 = new Notification.Builder(mContext, "test");
+        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test");
+        n2 = new Notification.Builder(mContext, "test")
+                .setContent(mock(RemoteViews.class));
+        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test")
+                .setCustomBigContentView(mock(RemoteViews.class));
+        n2 = new Notification.Builder(mContext, "test");
+        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test");
+        n2 = new Notification.Builder(mContext, "test")
+                .setCustomBigContentView(mock(RemoteViews.class));
+        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test");
+        n2 = new Notification.Builder(mContext, "test");
+        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+    }
+
+    @Test
+    public void testRemoteViews_layoutChange() {
+        RemoteViews a = mock(RemoteViews.class);
+        when(a.getLayoutId()).thenReturn(234);
+        RemoteViews b = mock(RemoteViews.class);
+        when(b.getLayoutId()).thenReturn(189);
+
+        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
+        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
+        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
+        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
+        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
+        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
+        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+    }
+
+    @Test
+    public void testRemoteViews_layoutSame() {
+        RemoteViews a = mock(RemoteViews.class);
+        when(a.getLayoutId()).thenReturn(234);
+        RemoteViews b = mock(RemoteViews.class);
+        when(b.getLayoutId()).thenReturn(234);
+
+        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
+        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
+        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
+        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
+        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
+        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
+        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+    }
+
+    @Test
+    public void testRemoteViews_sequenceChange() {
+        RemoteViews a = mock(RemoteViews.class);
+        when(a.getLayoutId()).thenReturn(234);
+        when(a.getSequenceNumber()).thenReturn(1);
+        RemoteViews b = mock(RemoteViews.class);
+        when(b.getLayoutId()).thenReturn(234);
+        when(b.getSequenceNumber()).thenReturn(2);
+
+        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
+        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
+        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
+        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
+        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
+        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
+        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+    }
+
+    @Test
+    public void testRemoteViews_sequenceSame() {
+        RemoteViews a = mock(RemoteViews.class);
+        when(a.getLayoutId()).thenReturn(234);
+        when(a.getSequenceNumber()).thenReturn(1);
+        RemoteViews b = mock(RemoteViews.class);
+        when(b.getLayoutId()).thenReturn(234);
+        when(b.getSequenceNumber()).thenReturn(1);
+
+        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
+        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
+        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
+        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
+        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
+        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
+        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+    }
+
+    @Test
+    public void testActionsDifferent_null() {
+        Notification n1 = new Notification.Builder(mContext, "test")
+                .build();
+        Notification n2 = new Notification.Builder(mContext, "test")
+                .build();
+
+        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testActionsDifferentSame() {
+        PendingIntent intent = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+        Notification n1 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
+                .build();
+        Notification n2 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
+                .build();
+
+        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testActionsDifferentText() {
+        PendingIntent intent = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+        Notification n1 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
+                .build();
+        Notification n2 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build())
+                .build();
+
+        assertTrue(Notification.areActionsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testActionsDifferentSpannables() {
+        PendingIntent intent = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+        Notification n1 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon,
+                        new SpannableStringBuilder().append("test1",
+                                new StyleSpan(Typeface.BOLD),
+                                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE),
+                        intent).build())
+                .build();
+        Notification n2 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "test1", intent).build())
+                .build();
+
+        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testActionsDifferentNumber() {
+        PendingIntent intent = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
+        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+        Notification n1 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
+                .build();
+        Notification n2 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
+                .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build())
+                .build();
+
+        assertTrue(Notification.areActionsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testActionsDifferentIntent() {
+        PendingIntent intent1 = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
+        PendingIntent intent2 = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
+        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+        Notification n1 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent1).build())
+                .build();
+        Notification n2 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent2).build())
+                .build();
+
+        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testActionsIgnoresRemoteInputs() {
+        PendingIntent intent = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+        Notification n1 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
+                        .addRemoteInput(new RemoteInput.Builder("a")
+                                .setChoices(new CharSequence[] {"i", "m"})
+                                .build())
+                        .build())
+                .build();
+        Notification n2 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
+                        .addRemoteInput(new RemoteInput.Builder("a")
+                                .setChoices(new CharSequence[] {"t", "m"})
+                                .build())
+                        .build())
+                .build();
+
+        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testFreeformRemoteInputActionPair_noRemoteInput() {
+        PendingIntent intent = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+        Notification notification = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
+                        .build())
+                .build();
+        Assert.assertNull(notification.findRemoteInputActionPair(false));
+    }
+
+    @Test
+    public void testFreeformRemoteInputActionPair_hasRemoteInput() {
+        PendingIntent intent = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+        RemoteInput remoteInput = new RemoteInput.Builder("a").build();
+
+        Notification.Action actionWithRemoteInput =
+                new Notification.Action.Builder(icon, "TEXT 1", intent)
+                        .addRemoteInput(remoteInput)
+                        .addRemoteInput(remoteInput)
+                        .build();
+
+        Notification.Action actionWithoutRemoteInput =
+                new Notification.Action.Builder(icon, "TEXT 2", intent)
+                        .build();
+
+        Notification notification = new Notification.Builder(mContext, "test")
+                .addAction(actionWithoutRemoteInput)
+                .addAction(actionWithRemoteInput)
+                .build();
+
+        Pair<RemoteInput, Notification.Action> remoteInputActionPair =
+                notification.findRemoteInputActionPair(false);
+
+        assertNotNull(remoteInputActionPair);
+        Assert.assertEquals(remoteInput, remoteInputActionPair.first);
+        Assert.assertEquals(actionWithRemoteInput, remoteInputActionPair.second);
+    }
+
+    @Test
+    public void testFreeformRemoteInputActionPair_requestFreeform_noFreeformRemoteInput() {
+        PendingIntent intent = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+        Notification notification = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
+                        .addRemoteInput(
+                                new RemoteInput.Builder("a")
+                                        .setAllowFreeFormInput(false).build())
+                        .build())
+                .build();
+        Assert.assertNull(notification.findRemoteInputActionPair(true));
+    }
+
+    @Test
+    public void testFreeformRemoteInputActionPair_requestFreeform_hasFreeformRemoteInput() {
+        PendingIntent intent = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+        RemoteInput remoteInput =
+                new RemoteInput.Builder("a").setAllowFreeFormInput(false).build();
+        RemoteInput freeformRemoteInput =
+                new RemoteInput.Builder("b").setAllowFreeFormInput(true).build();
+
+        Notification.Action actionWithFreeformRemoteInput =
+                new Notification.Action.Builder(icon, "TEXT 1", intent)
+                        .addRemoteInput(remoteInput)
+                        .addRemoteInput(freeformRemoteInput)
+                        .build();
+
+        Notification.Action actionWithoutFreeformRemoteInput =
+                new Notification.Action.Builder(icon, "TEXT 2", intent)
+                        .addRemoteInput(remoteInput)
+                        .build();
+
+        Notification notification = new Notification.Builder(mContext, "test")
+                .addAction(actionWithoutFreeformRemoteInput)
+                .addAction(actionWithFreeformRemoteInput)
+                .build();
+
+        Pair<RemoteInput, Notification.Action> remoteInputActionPair =
+                notification.findRemoteInputActionPair(true);
+
+        assertNotNull(remoteInputActionPair);
+        Assert.assertEquals(freeformRemoteInput, remoteInputActionPair.first);
+        Assert.assertEquals(actionWithFreeformRemoteInput, remoteInputActionPair.second);
+    }
+
     private void assertValid(Notification.Colors c) {
         // Assert that all colors are populated
         assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID);
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index f448cb3..f370ebd 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -60,6 +60,8 @@
     private OnBackAnimationCallback mCallback1;
     @Mock
     private OnBackAnimationCallback mCallback2;
+    @Mock
+    private BackEvent mBackEvent;
 
     @Before
     public void setUp() throws Exception {
@@ -85,14 +87,14 @@
         verify(mWindowSession, times(2)).setOnBackInvokedCallbackInfo(
                 Mockito.eq(mWindow),
                 captor.capture());
-        captor.getAllValues().get(0).getCallback().onBackStarted();
+        captor.getAllValues().get(0).getCallback().onBackStarted(mBackEvent);
         waitForIdle();
-        verify(mCallback1).onBackStarted();
+        verify(mCallback1).onBackStarted(mBackEvent);
         verifyZeroInteractions(mCallback2);
 
-        captor.getAllValues().get(1).getCallback().onBackStarted();
+        captor.getAllValues().get(1).getCallback().onBackStarted(mBackEvent);
         waitForIdle();
-        verify(mCallback2).onBackStarted();
+        verify(mCallback2).onBackStarted(mBackEvent);
         verifyNoMoreInteractions(mCallback1);
     }
 
@@ -110,9 +112,9 @@
                 Mockito.eq(mWindow), captor.capture());
         verifyNoMoreInteractions(mWindowSession);
         assertEquals(captor.getValue().getPriority(), OnBackInvokedDispatcher.PRIORITY_OVERLAY);
-        captor.getValue().getCallback().onBackStarted();
+        captor.getValue().getCallback().onBackStarted(mBackEvent);
         waitForIdle();
-        verify(mCallback1).onBackStarted();
+        verify(mCallback1).onBackStarted(mBackEvent);
     }
 
     @Test
@@ -148,8 +150,8 @@
         mDispatcher.registerOnBackInvokedCallback(
                 OnBackInvokedDispatcher.PRIORITY_OVERLAY, mCallback2);
         verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture());
-        captor.getValue().getCallback().onBackStarted();
+        captor.getValue().getCallback().onBackStarted(mBackEvent);
         waitForIdle();
-        verify(mCallback2).onBackStarted();
+        verify(mCallback2).onBackStarted(mBackEvent);
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index d19f9f5..52feac5 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -47,6 +47,7 @@
 import android.telephony.ModemActivityInfo;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
+import android.util.Log;
 import android.util.MutableInt;
 import android.util.SparseIntArray;
 import android.util.SparseLongArray;
@@ -82,6 +83,7 @@
  *      com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
  */
 public class BatteryStatsNoteTest extends TestCase {
+    private static final String TAG = BatteryStatsNoteTest.class.getSimpleName();
 
     private static final int UID = 10500;
     private static final int ISOLATED_APP_ID = Process.FIRST_ISOLATED_UID + 23;
@@ -2031,6 +2033,115 @@
                 noRadioProcFlags, lastProcStateChangeFlags.value);
     }
 
+
+
+    @SmallTest
+    public void testNoteMobileRadioPowerStateLocked() {
+        long curr;
+        boolean update;
+        final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
+        final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        bi.setOnBatteryInternal(true);
+
+        // Note mobile radio is on.
+        curr = 1000L * (clocks.realtime = clocks.uptime = 1001);
+        bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr,
+                UID);
+
+        // Note mobile radio is still on.
+        curr = 1000L * (clocks.realtime = clocks.uptime = 2001);
+        update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                curr, UID);
+        assertFalse(
+                "noteMobileRadioPowerStateLocked should not request an update when the power "
+                        + "state does not change from HIGH.",
+                update);
+
+        // Note mobile radio is off.
+        curr = 1000L * (clocks.realtime = clocks.uptime = 3001);
+        update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+                curr, UID);
+        assertTrue(
+                "noteMobileRadioPowerStateLocked should request an update when the power state "
+                        + "changes from HIGH to LOW.",
+                update);
+
+        // Note mobile radio is still off.
+        curr = 1000L * (clocks.realtime = clocks.uptime = 4001);
+        update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+                curr, UID);
+        assertFalse(
+                "noteMobileRadioPowerStateLocked should not request an update when the power "
+                        + "state does not change from LOW.",
+                update);
+
+        // Note mobile radio is on.
+        curr = 1000L * (clocks.realtime = clocks.uptime = 5001);
+        update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                curr, UID);
+        assertFalse(
+                "noteMobileRadioPowerStateLocked should not request an update when the power "
+                        + "state changes from LOW to HIGH.",
+                update);
+    }
+
+    @SmallTest
+    public void testNoteMobileRadioPowerStateLocked_rateLimited() {
+        long curr;
+        boolean update;
+        final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
+        final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        bi.setPowerProfile(mock(PowerProfile.class));
+
+        final int txLevelCount = CellSignalStrength.getNumSignalStrengthLevels();
+        final ModemActivityInfo mai = new ModemActivityInfo(0L, 0L, 0L, new int[txLevelCount], 0L);
+
+        final long rateLimit = bi.getMobileRadioPowerStateUpdateRateLimit();
+        if (rateLimit < 0) {
+            Log.w(TAG, "Skipping testNoteMobileRadioPowerStateLocked_rateLimited, rateLimit = "
+                    + rateLimit);
+            return;
+        }
+        bi.setOnBatteryInternal(true);
+
+        // Note mobile radio is on.
+        curr = 1000L * (clocks.realtime = clocks.uptime = 1001);
+        bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr,
+                UID);
+
+        clocks.realtime = clocks.uptime = 2001;
+        mai.setTimestamp(clocks.realtime);
+        bi.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE,
+                clocks.realtime, clocks.uptime, mNetworkStatsManager);
+
+        // Note mobile radio is off within the rate limit duration.
+        clocks.realtime = clocks.uptime = clocks.realtime + (long) (rateLimit * 0.7);
+        curr = 1000L * clocks.realtime;
+        update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+                curr, UID);
+        assertFalse(
+                "noteMobileRadioPowerStateLocked should not request an update when the power "
+                        + "state so soon after a noteModemControllerActivity",
+                update);
+
+        // Note mobile radio is on.
+        clocks.realtime = clocks.uptime = clocks.realtime + (long) (rateLimit * 0.7);
+        curr = 1000L * clocks.realtime;
+        bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr,
+                UID);
+
+        // Note mobile radio is off much later
+        clocks.realtime = clocks.uptime = clocks.realtime + rateLimit;
+        curr = 1000L * clocks.realtime;
+        update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+                curr, UID);
+        assertTrue(
+                "noteMobileRadioPowerStateLocked should request an update when the power state "
+                        + "changes from HIGH to LOW much later after a "
+                        + "noteModemControllerActivity.",
+                update);
+    }
+
     private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) {
         // Note that noteUidProcessStateLocked uses ActivityManager process states.
         if (fgOn) {
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index edeb5e9..5ea4f06 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -114,6 +114,10 @@
         return getUidStatsLocked(uid).mOnBatteryScreenOffBackgroundTimeBase;
     }
 
+    public long getMobileRadioPowerStateUpdateRateLimit() {
+        return MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS;
+    }
+
     public MockBatteryStatsImpl setNetworkStats(NetworkStats networkStats) {
         mNetworkStats = networkStats;
         return this;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 1174b68..bf7326a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -20,10 +20,11 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO;
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
-import static android.window.TaskFragmentOrganizer.getTransitionType;
 import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
@@ -76,6 +77,7 @@
 import androidx.window.common.CommonFoldingFeature;
 import androidx.window.common.EmptyLifecycleCallbacksAdapter;
 import androidx.window.extensions.WindowExtensionsProvider;
+import androidx.window.extensions.embedding.TransactionManager.TransactionRecord;
 import androidx.window.extensions.layout.WindowLayoutComponentImpl;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -100,6 +102,10 @@
     @GuardedBy("mLock")
     final SplitPresenter mPresenter;
 
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    final TransactionManager mTransactionManager;
+
     // Currently applied split configuration.
     @GuardedBy("mLock")
     private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
@@ -150,6 +156,7 @@
         final MainThreadExecutor executor = new MainThreadExecutor();
         mHandler = executor.mHandler;
         mPresenter = new SplitPresenter(executor, this);
+        mTransactionManager = new TransactionManager(mPresenter);
         final ActivityThread activityThread = ActivityThread.currentActivityThread();
         final Application application = activityThread.getApplication();
         // Register a callback to be notified about activities being created.
@@ -167,7 +174,9 @@
         @Override
         public void accept(List<CommonFoldingFeature> foldingFeatures) {
             synchronized (mLock) {
-                final WindowContainerTransaction wct = new WindowContainerTransaction();
+                final TransactionRecord transactionRecord = mTransactionManager
+                        .startNewTransaction();
+                final WindowContainerTransaction wct = transactionRecord.getTransaction();
                 for (int i = 0; i < mTaskContainers.size(); i++) {
                     final TaskContainer taskContainer = mTaskContainers.valueAt(i);
                     if (!taskContainer.isVisible()) {
@@ -186,7 +195,9 @@
                     updateContainersInTask(wct, taskContainer);
                     updateAnimationOverride(taskContainer);
                 }
-                mPresenter.applyTransaction(wct);
+                // The WCT should be applied and merged to the device state change transition if
+                // there is one.
+                transactionRecord.apply(false /* shouldApplyIndependently */);
             }
         }
     }
@@ -256,7 +267,9 @@
     @Override
     public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
         synchronized (mLock) {
-            final WindowContainerTransaction wct = new WindowContainerTransaction();
+            final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(
+                    transaction.getTransactionToken());
+            final WindowContainerTransaction wct = transactionRecord.getTransaction();
             final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
             for (TaskFragmentTransaction.Change change : changes) {
                 final int taskId = change.getTaskId();
@@ -307,8 +320,7 @@
 
             // Notify the server, and the server should apply and merge the
             // WindowContainerTransaction to the active sync to finish the TaskFragmentTransaction.
-            mPresenter.onTransactionHandled(transaction.getTransactionToken(), wct,
-                    getTransitionType(wct), false /* shouldApplyIndependently */);
+            transactionRecord.apply(false /* shouldApplyIndependently */);
             updateCallbackIfNecessary();
         }
     }
@@ -333,6 +345,7 @@
 
         container.setInfo(wct, taskFragmentInfo);
         if (container.isFinished()) {
+            mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
             mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
         } else {
             // Update with the latest Task configuration.
@@ -368,15 +381,18 @@
                 // Do not finish the dependents if the last activity is reparented to PiP.
                 // Instead, the original split should be cleanup, and the dependent may be
                 // expanded to fullscreen.
+                mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
                 cleanupForEnterPip(wct, container);
                 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
             } else if (taskFragmentInfo.isTaskClearedForReuse()) {
                 // Do not finish the dependents if this TaskFragment was cleared due to
                 // launching activity in the Task.
+                mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
                 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
             } else if (!container.isWaitingActivityAppear()) {
                 // Do not finish the container before the expected activity appear until
                 // timeout.
+                mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
                 mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */);
             }
         } else if (wasInPip && isInPip) {
@@ -571,6 +587,7 @@
                 container.setInfo(wct, taskFragmentInfo);
                 container.clearPendingAppearedActivities();
                 if (container.isEmpty()) {
+                    mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
                     mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
                 }
                 break;
@@ -1009,11 +1026,10 @@
      */
     @GuardedBy("mLock")
     void onTaskFragmentAppearEmptyTimeout(@NonNull TaskFragmentContainer container) {
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-        onTaskFragmentAppearEmptyTimeout(wct, container);
+        final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+        onTaskFragmentAppearEmptyTimeout(transactionRecord.getTransaction(), container);
         // Can be applied independently as a timeout callback.
-        mPresenter.applyTransaction(wct, getTransitionType(wct),
-                true /* shouldApplyIndependently */);
+        transactionRecord.apply(true /* shouldApplyIndependently */);
     }
 
     /**
@@ -1023,6 +1039,7 @@
     @GuardedBy("mLock")
     void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct,
             @NonNull TaskFragmentContainer container) {
+        mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
         mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
     }
 
@@ -1562,6 +1579,7 @@
      * @param isOnCreated       whether this happens during the primary activity onCreated.
      */
     @VisibleForTesting
+    @GuardedBy("mLock")
     @Nullable
     Bundle getPlaceholderOptions(@NonNull Activity primaryActivity, boolean isOnCreated) {
         // Setting avoid move to front will also skip the animation. We only want to do that when
@@ -1569,6 +1587,8 @@
         // Check if the primary is resumed or if this is called when the primary is onCreated
         // (not resumed yet).
         if (isOnCreated || primaryActivity.isResumed()) {
+            // Only set trigger type if the launch happens in foreground.
+            mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_OPEN);
             return null;
         }
         final ActivityOptions options = ActivityOptions.makeBasic();
@@ -1595,6 +1615,8 @@
         if (SplitPresenter.shouldShowSplit(splitAttributes)) {
             return false;
         }
+
+        mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
         mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(),
                 false /* shouldFinishDependent */);
         return true;
@@ -1905,23 +1927,26 @@
             // that we don't launch it if an activity itself already requested something to be
             // launched to side.
             synchronized (mLock) {
-                final WindowContainerTransaction wct = new WindowContainerTransaction();
-                SplitController.this.onActivityCreated(wct, activity);
+                final TransactionRecord transactionRecord = mTransactionManager
+                        .startNewTransaction();
+                transactionRecord.setOriginType(TRANSIT_OPEN);
+                SplitController.this.onActivityCreated(transactionRecord.getTransaction(),
+                        activity);
                 // The WCT should be applied and merged to the activity launch transition.
-                mPresenter.applyTransaction(wct, getTransitionType(wct),
-                        false /* shouldApplyIndependently */);
+                transactionRecord.apply(false /* shouldApplyIndependently */);
             }
         }
 
         @Override
         public void onActivityConfigurationChanged(@NonNull Activity activity) {
             synchronized (mLock) {
-                final WindowContainerTransaction wct = new WindowContainerTransaction();
-                SplitController.this.onActivityConfigurationChanged(wct, activity);
+                final TransactionRecord transactionRecord = mTransactionManager
+                        .startNewTransaction();
+                SplitController.this.onActivityConfigurationChanged(
+                        transactionRecord.getTransaction(), activity);
                 // The WCT should be applied and merged to the Task change transition so that the
                 // placeholder is launched in the same transition.
-                mPresenter.applyTransaction(wct, getTransitionType(wct),
-                        false /* shouldApplyIndependently */);
+                transactionRecord.apply(false /* shouldApplyIndependently */);
             }
         }
 
@@ -1977,7 +2002,10 @@
             }
 
             synchronized (mLock) {
-                final WindowContainerTransaction wct = new WindowContainerTransaction();
+                final TransactionRecord transactionRecord = mTransactionManager
+                        .startNewTransaction();
+                transactionRecord.setOriginType(TRANSIT_OPEN);
+                final WindowContainerTransaction wct = transactionRecord.getTransaction();
                 final TaskFragmentContainer launchedInTaskFragment;
                 if (launchingActivity != null) {
                     final int taskId = getTaskId(launchingActivity);
@@ -1990,13 +2018,14 @@
                 if (launchedInTaskFragment != null) {
                     // Make sure the WCT is applied immediately instead of being queued so that the
                     // TaskFragment will be ready before activity attachment.
-                    mPresenter.applyTransaction(wct, getTransitionType(wct),
-                            false /* shouldApplyIndependently */);
+                    transactionRecord.apply(false /* shouldApplyIndependently */);
                     // Amend the request to let the WM know that the activity should be placed in
                     // the dedicated container.
                     options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
                             launchedInTaskFragment.getTaskFragmentToken());
                     mCurrentIntent = intent;
+                } else {
+                    transactionRecord.abort();
                 }
             }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java
new file mode 100644
index 0000000..0071fea
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java
@@ -0,0 +1,201 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_NONE;
+
+import android.os.IBinder;
+import android.view.WindowManager.TransitionType;
+import android.window.TaskFragmentOrganizer;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Responsible for managing the current {@link WindowContainerTransaction} as a response to device
+ * state changes and app interactions.
+ *
+ * A typical use flow:
+ * 1. Call {@link #startNewTransaction} to start tracking the changes.
+ * 2. Use {@link TransactionRecord#setOriginType(int)} (int)} to record the type of operation that
+ *    will start a new transition on system server.
+ * 3. Use {@link #getCurrentTransactionRecord()} to get current {@link TransactionRecord} for
+ *    changes.
+ * 4. Call {@link TransactionRecord#apply(boolean)} to request the system server to apply changes in
+ *    the current {@link WindowContainerTransaction}, or call {@link TransactionRecord#abort()} to
+ *    dispose the current one.
+ *
+ * Note:
+ * There should be only one transaction at a time. The caller should not call
+ * {@link #startNewTransaction} again before calling {@link TransactionRecord#apply(boolean)} or
+ * {@link TransactionRecord#abort()} to the previous transaction.
+ */
+class TransactionManager {
+
+    @NonNull
+    private final TaskFragmentOrganizer mOrganizer;
+
+    @Nullable
+    private TransactionRecord mCurrentTransaction;
+
+    TransactionManager(@NonNull TaskFragmentOrganizer organizer) {
+        mOrganizer = organizer;
+    }
+
+    @NonNull
+    TransactionRecord startNewTransaction() {
+        return startNewTransaction(null /* taskFragmentTransactionToken */);
+    }
+
+    /**
+     * Starts tracking the changes in a new {@link WindowContainerTransaction}. Caller can call
+     * {@link #getCurrentTransactionRecord()} later to continue adding change to the current
+     * transaction until {@link TransactionRecord#apply(boolean)} or
+     * {@link TransactionRecord#abort()} is called.
+     * @param taskFragmentTransactionToken  {@link android.window.TaskFragmentTransaction
+     *                                      #getTransactionToken()} if this is a response to a
+     *                                      {@link android.window.TaskFragmentTransaction}.
+     */
+    @NonNull
+    TransactionRecord startNewTransaction(@Nullable IBinder taskFragmentTransactionToken) {
+        if (mCurrentTransaction != null) {
+            mCurrentTransaction = null;
+            throw new IllegalStateException(
+                    "The previous transaction has not been applied or aborted,");
+        }
+        mCurrentTransaction = new TransactionRecord(taskFragmentTransactionToken);
+        return mCurrentTransaction;
+    }
+
+    /**
+     * Gets the current {@link TransactionRecord} started from {@link #startNewTransaction}.
+     */
+    @NonNull
+    TransactionRecord getCurrentTransactionRecord() {
+        if (mCurrentTransaction == null) {
+            throw new IllegalStateException("startNewTransaction() is not invoked before calling"
+                    + " getCurrentTransactionRecord().");
+        }
+        return mCurrentTransaction;
+    }
+
+    /** The current transaction. The manager should only handle one transaction at a time. */
+    class TransactionRecord {
+        /**
+         * {@link WindowContainerTransaction} containing the current change.
+         * @see #startNewTransaction(IBinder)
+         * @see #apply (boolean)
+         */
+        @NonNull
+        private final WindowContainerTransaction mTransaction = new WindowContainerTransaction();
+
+        /**
+         * If the current transaction is a response to a
+         * {@link android.window.TaskFragmentTransaction}, this is the
+         * {@link android.window.TaskFragmentTransaction#getTransactionToken()}.
+         * @see #startNewTransaction(IBinder)
+         */
+        @Nullable
+        private final IBinder mTaskFragmentTransactionToken;
+
+        /**
+         * To track of the origin type of the current {@link #mTransaction}. When
+         * {@link #apply (boolean)} to start a new transition, this is the type to request.
+         * @see #setOriginType(int)
+         * @see #getTransactionTransitionType()
+         */
+        @TransitionType
+        private int mOriginType = TRANSIT_NONE;
+
+        TransactionRecord(@Nullable IBinder taskFragmentTransactionToken) {
+            mTaskFragmentTransactionToken = taskFragmentTransactionToken;
+        }
+
+        @NonNull
+        WindowContainerTransaction getTransaction() {
+            ensureCurrentTransaction();
+            return mTransaction;
+        }
+
+        /**
+         * Sets the {@link TransitionType} that triggers this transaction. If there are multiple
+         * calls, only the first call will be respected as the "origin" type.
+         */
+        void setOriginType(@TransitionType int type) {
+            ensureCurrentTransaction();
+            if (mOriginType != TRANSIT_NONE) {
+                // Skip if the origin type has already been set.
+                return;
+            }
+            mOriginType = type;
+        }
+
+        /**
+         * Requests the system server to apply the current transaction started from
+         * {@link #startNewTransaction}.
+         * @param shouldApplyIndependently  If {@code true}, the {@link #mCurrentTransaction} will
+         *                                  request a new transition, which will be queued until the
+         *                                  sync engine is free if there is any other active sync.
+         *                                  If {@code false}, the {@link #startNewTransaction} will
+         *                                  be directly applied to the active sync.
+         */
+        void apply(boolean shouldApplyIndependently) {
+            ensureCurrentTransaction();
+            if (mTaskFragmentTransactionToken != null) {
+                // If this is a response to a TaskFragmentTransaction.
+                mOrganizer.onTransactionHandled(mTaskFragmentTransactionToken, mTransaction,
+                        getTransactionTransitionType(), shouldApplyIndependently);
+            } else {
+                mOrganizer.applyTransaction(mTransaction, getTransactionTransitionType(),
+                        shouldApplyIndependently);
+            }
+            dispose();
+        }
+
+        /** Called when there is no need to {@link #apply(boolean)} the current transaction. */
+        void abort() {
+            ensureCurrentTransaction();
+            dispose();
+        }
+
+        private void dispose() {
+            TransactionManager.this.mCurrentTransaction = null;
+        }
+
+        private void ensureCurrentTransaction() {
+            if (TransactionManager.this.mCurrentTransaction != this) {
+                throw new IllegalStateException(
+                        "This transaction has already been apply() or abort().");
+            }
+        }
+
+        /**
+         * Gets the {@link TransitionType} that we will request transition with for the
+         * current {@link WindowContainerTransaction}.
+         */
+        @VisibleForTesting
+        @TransitionType
+        int getTransactionTransitionType() {
+            // Use TRANSIT_CHANGE as default if there is not opening/closing window.
+            return mOriginType != TRANSIT_NONE ? mOriginType : TRANSIT_CHANGE;
+        }
+    }
+}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 25d0347..a403031 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -132,6 +132,7 @@
 
     private SplitController mSplitController;
     private SplitPresenter mSplitPresenter;
+    private TransactionManager mTransactionManager;
 
     @Before
     public void setUp() {
@@ -140,8 +141,10 @@
                 .getCurrentWindowLayoutInfo(anyInt(), any());
         mSplitController = new SplitController(mWindowLayoutComponent);
         mSplitPresenter = mSplitController.mPresenter;
+        mTransactionManager = mSplitController.mTransactionManager;
         spyOn(mSplitController);
         spyOn(mSplitPresenter);
+        spyOn(mTransactionManager);
         doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
         final Configuration activityConfig = new Configuration();
         activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
@@ -212,6 +215,8 @@
 
     @Test
     public void testOnTaskFragmentAppearEmptyTimeout() {
+        // Setup to make sure a transaction record is started.
+        mTransactionManager.startNewTransaction();
         final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
         doCallRealMethod().when(mSplitController).onTaskFragmentAppearEmptyTimeout(any(), any());
         mSplitController.onTaskFragmentAppearEmptyTimeout(mTransaction, tf);
@@ -615,6 +620,8 @@
 
     @Test
     public void testResolveActivityToContainer_placeholderRule_notInTaskFragment() {
+        // Setup to make sure a transaction record is started.
+        mTransactionManager.startNewTransaction();
         setupPlaceholderRule(mActivity);
         final SplitPlaceholderRule placeholderRule =
                 (SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
@@ -647,6 +654,8 @@
 
     @Test
     public void testResolveActivityToContainer_placeholderRule_inTopMostTaskFragment() {
+        // Setup to make sure a transaction record is started.
+        mTransactionManager.startNewTransaction();
         setupPlaceholderRule(mActivity);
         final SplitPlaceholderRule placeholderRule =
                 (SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
@@ -679,6 +688,8 @@
 
     @Test
     public void testResolveActivityToContainer_placeholderRule_inSecondarySplit() {
+        // Setup to make sure a transaction record is started.
+        mTransactionManager.startNewTransaction();
         setupPlaceholderRule(mActivity);
         final SplitPlaceholderRule placeholderRule =
                 (SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
@@ -961,6 +972,8 @@
 
     @Test
     public void testGetPlaceholderOptions() {
+        // Setup to make sure a transaction record is started.
+        mTransactionManager.startNewTransaction();
         doReturn(true).when(mActivity).isResumed();
 
         assertNull(mSplitController.getPlaceholderOptions(mActivity, false /* isOnCreated */));
@@ -1147,8 +1160,6 @@
                         + "of other properties",
                 SplitController.haveSamePresentation(splitRule1, splitRule2,
                         new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
-
-
     }
 
     /** Creates a mock activity in the organizer process. */
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java
new file mode 100644
index 0000000..62006bd
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java
@@ -0,0 +1,204 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.clearInvocations;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.window.TaskFragmentOrganizer;
+import android.window.WindowContainerTransaction;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.window.extensions.embedding.TransactionManager.TransactionRecord;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Test class for {@link TransactionManager}.
+ *
+ * Build/Install/Run:
+ *  atest WMJetpackUnitTests:TransactionManagerTest
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TransactionManagerTest {
+
+    @Mock
+    private TaskFragmentOrganizer mOrganizer;
+    private TransactionManager mTransactionManager;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mTransactionManager = new TransactionManager(mOrganizer);
+    }
+
+    @Test
+    public void testStartNewTransaction() {
+        mTransactionManager.startNewTransaction();
+
+        // Throw exception if #startNewTransaction is called twice without #apply() or #abort().
+        assertThrows(IllegalStateException.class, mTransactionManager::startNewTransaction);
+
+        // Allow to start new after #apply() the last transaction.
+        TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+        transactionRecord.apply(false /* shouldApplyIndependently */);
+        transactionRecord = mTransactionManager.startNewTransaction();
+
+        // Allow to start new after #abort() the last transaction.
+        transactionRecord.abort();
+        mTransactionManager.startNewTransaction();
+    }
+
+    @Test
+    public void testSetTransactionOriginType() {
+        // Return TRANSIT_CHANGE if there is no trigger type set.
+        TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+
+        assertEquals(TRANSIT_CHANGE, transactionRecord.getTransactionTransitionType());
+
+        // Return the first set type.
+        mTransactionManager.getCurrentTransactionRecord().abort();
+        transactionRecord = mTransactionManager.startNewTransaction();
+        transactionRecord.setOriginType(TRANSIT_OPEN);
+
+        assertEquals(TRANSIT_OPEN, transactionRecord.getTransactionTransitionType());
+
+        transactionRecord.setOriginType(TRANSIT_CLOSE);
+
+        assertEquals(TRANSIT_OPEN, transactionRecord.getTransactionTransitionType());
+
+        // Reset when #startNewTransaction().
+        transactionRecord.abort();
+        transactionRecord = mTransactionManager.startNewTransaction();
+
+        assertEquals(TRANSIT_CHANGE, transactionRecord.getTransactionTransitionType());
+    }
+
+    @Test
+    public void testGetCurrentTransactionRecord() {
+        // Throw exception if #getTransaction is called without calling #startNewTransaction().
+        assertThrows(IllegalStateException.class, mTransactionManager::getCurrentTransactionRecord);
+
+        TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+        assertNotNull(transactionRecord);
+
+        // Same WindowContainerTransaction should be returned.
+        assertSame(transactionRecord, mTransactionManager.getCurrentTransactionRecord());
+
+        // Reset after #abort().
+        transactionRecord.abort();
+        assertThrows(IllegalStateException.class, mTransactionManager::getCurrentTransactionRecord);
+
+        // New WindowContainerTransaction after #startNewTransaction().
+        mTransactionManager.startNewTransaction();
+        assertNotEquals(transactionRecord, mTransactionManager.getCurrentTransactionRecord());
+
+        // Reset after #apply().
+        mTransactionManager.getCurrentTransactionRecord().apply(
+                false /* shouldApplyIndependently */);
+        assertThrows(IllegalStateException.class, mTransactionManager::getCurrentTransactionRecord);
+    }
+
+    @Test
+    public void testApply() {
+        // #applyTransaction(false)
+        TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+        int transitionType = transactionRecord.getTransactionTransitionType();
+        WindowContainerTransaction wct = transactionRecord.getTransaction();
+        transactionRecord.apply(false /* shouldApplyIndependently */);
+
+        verify(mOrganizer).applyTransaction(wct, transitionType,
+                false /* shouldApplyIndependently */);
+
+        // #applyTransaction(true)
+        clearInvocations(mOrganizer);
+        transactionRecord = mTransactionManager.startNewTransaction();
+        transitionType = transactionRecord.getTransactionTransitionType();
+        wct = transactionRecord.getTransaction();
+        transactionRecord.apply(true /* shouldApplyIndependently */);
+
+        verify(mOrganizer).applyTransaction(wct, transitionType,
+                true /* shouldApplyIndependently */);
+
+        // #onTransactionHandled(false)
+        clearInvocations(mOrganizer);
+        IBinder token = new Binder();
+        transactionRecord = mTransactionManager.startNewTransaction(token);
+        transitionType = transactionRecord.getTransactionTransitionType();
+        wct = transactionRecord.getTransaction();
+        transactionRecord.apply(false /* shouldApplyIndependently */);
+
+        verify(mOrganizer).onTransactionHandled(token, wct, transitionType,
+                false /* shouldApplyIndependently */);
+
+        // #onTransactionHandled(true)
+        clearInvocations(mOrganizer);
+        token = new Binder();
+        transactionRecord = mTransactionManager.startNewTransaction(token);
+        transitionType = transactionRecord.getTransactionTransitionType();
+        wct = transactionRecord.getTransaction();
+        transactionRecord.apply(true /* shouldApplyIndependently */);
+
+        verify(mOrganizer).onTransactionHandled(token, wct, transitionType,
+                true /* shouldApplyIndependently */);
+
+        // Throw exception if there is any more interaction.
+        final TransactionRecord record = transactionRecord;
+        assertThrows(IllegalStateException.class,
+                () -> record.apply(false /* shouldApplyIndependently */));
+        assertThrows(IllegalStateException.class,
+                () -> record.apply(true /* shouldApplyIndependently */));
+        assertThrows(IllegalStateException.class,
+                record::abort);
+    }
+
+    @Test
+    public void testAbort() {
+        final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+        transactionRecord.abort();
+
+        // Throw exception if there is any more interaction.
+        verifyNoMoreInteractions(mOrganizer);
+        assertThrows(IllegalStateException.class,
+                () -> transactionRecord.apply(false /* shouldApplyIndependently */));
+        assertThrows(IllegalStateException.class,
+                () -> transactionRecord.apply(true /* shouldApplyIndependently */));
+        assertThrows(IllegalStateException.class,
+                transactionRecord::abort);
+    }
+}
diff --git a/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml
index 66e5b43..5ecba38 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml
@@ -22,8 +22,8 @@
         >
     <group android:scaleX="0.5"
            android:scaleY="0.5"
-           android:translateX="8.0"
-           android:translateY="8.0" >
+           android:translateX="4.0"
+           android:translateY="4.0" >
         <path
             android:fillColor="@android:color/black"
             android:pathData="MM24,40.3 L7.7,24 24,7.7 26.8,10.45 15.3,22H40.3V26H15.3L26.8,37.5Z"/>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml b/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml
index 53a8bb1..416287d 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml
@@ -17,4 +17,5 @@
 <shape android:shape="rectangle"
        xmlns:android="http://schemas.android.com/apk/res/android">
     <solid android:color="@android:color/white" />
+    <corners android:radius="20dp" />
 </shape>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml
index 851cbf2..cf9e632 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml
@@ -21,8 +21,8 @@
         android:viewportHeight="32.0">
     <group android:scaleX="0.5"
             android:scaleY="0.5"
-            android:translateX="8.0"
-            android:translateY="8.0" >
+            android:translateX="4.0"
+            android:translateY="4.0" >
         <path
             android:fillColor="@android:color/black"
             android:pathData="M12.45,38.35 L9.65,35.55 21.2,24 9.65,12.45 12.45,9.65 24,21.2 35.55,9.65 38.35,12.45 26.8,24 38.35,35.55 35.55,38.35 24,26.8Z"/>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
index ee0f466..c9f2623 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
@@ -18,6 +18,8 @@
         android:height="24dp"
         android:viewportWidth="24"
         android:viewportHeight="24">
-    <path
-        android:fillColor="@android:color/black" android:pathData="M3,5V3H21V5Z"/>
+    <group android:translateY="8.0">
+        <path
+            android:fillColor="@android:color/black" android:pathData="M3,5V3H21V5Z"/>
+    </group>
 </vector>
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index cc60074..f9d153f 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimeer"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Maak klein"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Maak toe"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Terug"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Handvatsel"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 7aa8770..17b1d1b 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"አስፋ"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"አሳንስ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ዝጋ"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"ተመለስ"</string>
+    <string name="handle_text" msgid="1766582106752184456">"መያዣ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index 2d0d970..2babc3e 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"تكبير"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"تصغير"</string>
     <string name="close_button_text" msgid="2913281996024033299">"إغلاق"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"رجوع"</string>
+    <string name="handle_text" msgid="1766582106752184456">"مقبض"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 42153ff..fe43cd6 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -37,14 +37,14 @@
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"গৌণ ডিছপ্লেত এপ্ লঞ্চ কৰিব নোৱাৰি।"</string>
     <string name="accessibility_divider" msgid="703810061635792791">"স্প্লিট স্ক্ৰীনৰ বিভাজক"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"বাওঁফালৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string>
-    <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"বাওঁফালৰ স্ক্ৰীণখন ৭০% কৰক"</string>
-    <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"বাওঁফালৰ স্ক্ৰীণখন ৫০% কৰক"</string>
-    <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"বাওঁফালৰ স্ক্ৰীণখন ৩০% কৰক"</string>
+    <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"বাওঁফালৰ স্ক্ৰীনখন ৭০% কৰক"</string>
+    <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"বাওঁফালৰ স্ক্ৰীনখন ৫০% কৰক"</string>
+    <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"বাওঁফালৰ স্ক্ৰীনখন ৩০% কৰক"</string>
     <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"সোঁফালৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string>
     <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"শীৰ্ষ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string>
-    <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"শীর্ষ স্ক্ৰীণখন ৭০% কৰক"</string>
-    <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"শীর্ষ স্ক্ৰীণখন ৫০% কৰক"</string>
-    <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"শীর্ষ স্ক্ৰীণখন ৩০% কৰক"</string>
+    <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"শীর্ষ স্ক্ৰীনখন ৭০% কৰক"</string>
+    <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"শীর্ষ স্ক্ৰীনখন ৫০% কৰক"</string>
+    <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"শীর্ষ স্ক্ৰীনখন ৩০% কৰক"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"তলৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"এখন হাতেৰে ব্যৱহাৰ কৰা ম’ড ব্যৱহাৰ কৰা"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"বাহিৰ হ’বলৈ স্ক্ৰীনখনৰ একেবাৰে তলৰ পৰা ওপৰলৈ ছোৱাইপ কৰক অথবা এপ্‌টোৰ ওপৰত যিকোনো ঠাইত টিপক"</string>
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"সৰ্বাধিক মাত্ৰালৈ বঢ়াওক"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"মিনিমাইজ কৰক"</string>
     <string name="close_button_text" msgid="2913281996024033299">"বন্ধ কৰক"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"উভতি যাওক"</string>
+    <string name="handle_text" msgid="1766582106752184456">"হেণ্ডেল"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 5da5a97..c22f542 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Böyüdün"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Kiçildin"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Bağlayın"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Geriyə"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Hər kəsə açıq istifadəçi adı"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
index aff2b9f..4dda1db 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Uvećajte"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Umanjite"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zatvorite"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Nazad"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index e325e51..cf5394b 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Разгарнуць"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Згарнуць"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Закрыць"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index 3bedc86..c525f52 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Увеличаване"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Намаляване"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Затваряне"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Манипулатор"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 35e5b38..b652d383 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"বড় করুন"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ছোট করুন"</string>
     <string name="close_button_text" msgid="2913281996024033299">"বন্ধ করুন"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"ফিরে যান"</string>
+    <string name="handle_text" msgid="1766582106752184456">"হাতল"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index 34b282b..bec284d 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziranje"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimiziranje"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zatvaranje"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Nazad"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index 0279917..c84bb48 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximitza"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimitza"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Tanca"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Enrere"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Ansa"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index d58100b..17f7374 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovat"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimalizovat"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zavřít"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Zpět"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Úchyt"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index abb9cea..60c1f85 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimér"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimer"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Luk"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Tilbage"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Håndtag"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index a579394..b57f0c8 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximieren"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimieren"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Schließen"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Zurück"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Ziehpunkt"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index 248cff8..2f92946 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Μεγιστοποίηση"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Ελαχιστοποίηση"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Κλείσιμο"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Πίσω"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Λαβή"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index 9c798b8..9c88e78 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Back"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index 9c798b8..9c88e78 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Back"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index 9c798b8..9c88e78 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Back"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index 9c798b8..9c88e78 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Back"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
index 5ff5c58..646b3c5 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‎‎‎‏‎‏‎‎‎‎‎‏‎‎‎‏‎‎‏‎‎‎‎‎‎‎‎‎‎‎‎‎‏‎‏‏‎Maximize‎‏‎‎‏‎"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‏‏‎‎‎‏‎‎‏‏‏‎‎‎‏‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‏‏‎‏‏‏‏‏‎‏‏‎‏‏‏‎‏‏‎‎‏‎Minimize‎‏‎‎‏‎"</string>
     <string name="close_button_text" msgid="2913281996024033299">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‏‎‏‏‏‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‏‎‎‏‏‎Close‎‏‎‎‏‎"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‏‏‎‎‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‎‎‏‎‎‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‏‏‏‏‏‏‎‏‎Back‎‏‎‎‏‎"</string>
+    <string name="handle_text" msgid="1766582106752184456">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‎‏‎‎‎‎‏‎‏‎‎‏‎‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‎‎‏‎‎‎‎Handle‎‏‎‎‏‎"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index 08a7f49..aed03ac 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index 139232c..bddc2c3 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index d6f3580..696a520 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimeeri"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimeeri"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Sule"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Tagasi"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Käepide"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index e0efadd6..d1fca9b 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizatu"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizatu"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Itxi"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Atzera"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Kontu-izena"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index 267dd20..7550a09 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"بزرگ کردن"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"کوچک کردن"</string>
     <string name="close_button_text" msgid="2913281996024033299">"بستن"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"برگشتن"</string>
+    <string name="handle_text" msgid="1766582106752184456">"دستگیره"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index 30321a2..a79adf8 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Suurenna"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Pienennä"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Sulje"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Takaisin"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Kahva"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index 1278997..4c59b99 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Réduire"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Fermer"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Retour"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Identifiant"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index 84c8618..865e2dc 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Réduire"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Fermer"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Retour"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Poignée"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index 59ec2de..47e6696 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Pechar"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index 85d98ed..5d52c58 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"મોટું કરો"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"નાનું કરો"</string>
     <string name="close_button_text" msgid="2913281996024033299">"બંધ કરો"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"પાછળ"</string>
+    <string name="handle_text" msgid="1766582106752184456">"હૅન્ડલ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index bd90b7e..2c643de 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"बड़ा करें"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"विंडो छोटी करें"</string>
     <string name="close_button_text" msgid="2913281996024033299">"बंद करें"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"वापस जाएं"</string>
+    <string name="handle_text" msgid="1766582106752184456">"हैंडल"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index 41df0df..32ab87c 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziraj"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimiziraj"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zatvori"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Natrag"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Pokazivač"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index 9736a69..dba7f4e 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Teljes méret"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Kis méret"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Bezárás"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Vissza"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Fogópont"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 3c64494..d3bca33 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Ծավալել"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Ծալել"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Փակել"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Հետ"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Նշիչ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index 61f3903..f157fcf 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimalkan"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimalkan"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Tutup"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Kembali"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Tuas"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index 044348c..ead1757 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Stækka"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minnka"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Loka"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Til baka"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Handfang"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index ca97693..4e8ac62 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Ingrandisci"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Riduci a icona"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Chiudi"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Indietro"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 67de8a0..12c962d 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"הגדלה"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"מזעור"</string>
     <string name="close_button_text" msgid="2913281996024033299">"סגירה"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"חזרה"</string>
+    <string name="handle_text" msgid="1766582106752184456">"נקודת אחיזה"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index cc8527e..d9f514a 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
     <string name="close_button_text" msgid="2913281996024033299">"閉じる"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"戻る"</string>
+    <string name="handle_text" msgid="1766582106752184456">"ハンドル"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index 0ef4563..b284361 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"მაქსიმალურად გაშლა"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ჩაკეცვა"</string>
     <string name="close_button_text" msgid="2913281996024033299">"დახურვა"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"უკან"</string>
+    <string name="handle_text" msgid="1766582106752184456">"იდენტიფიკატორი"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index 6ba9a68..2d842b8 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Жаю"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Кішірейту"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Жабу"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Артқа"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Идентификатор"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 03682d8..3243a64 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"ពង្រីក"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"បង្រួម"</string>
     <string name="close_button_text" msgid="2913281996024033299">"បិទ"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"ថយក្រោយ"</string>
+    <string name="handle_text" msgid="1766582106752184456">"ឈ្មោះអ្នកប្រើប្រាស់"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index 167a38e..57c7124 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"ಹಿಗ್ಗಿಸಿ"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ಕುಗ್ಗಿಸಿ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ಮುಚ್ಚಿರಿ"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"ಹಿಂದಕ್ಕೆ"</string>
+    <string name="handle_text" msgid="1766582106752184456">"ಹ್ಯಾಂಡಲ್"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index 1175a0e..3a655b8 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"최대화"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"최소화"</string>
     <string name="close_button_text" msgid="2913281996024033299">"닫기"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"뒤로"</string>
+    <string name="handle_text" msgid="1766582106752184456">"핸들"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index b047863..ab51ca0 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Чоңойтуу"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Кичирейтүү"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Жабуу"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Артка"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 00bf7f4..b800e3e 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"ຂະຫຍາຍໃຫຍ່ສຸດ"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ຫຍໍ້ລົງ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ປິດ"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"ກັບຄືນ"</string>
+    <string name="handle_text" msgid="1766582106752184456">"ມືບັງຄັບ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index 3981793..94339a4 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Padidinti"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Sumažinti"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Uždaryti"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Atgal"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Rankenėlė"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index 3599c14..d282453 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimizēt"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizēt"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Aizvērt"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Atpakaļ"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Turis"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index 6c41b0c..64ce3f6 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Зголеми"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Минимизирај"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Затвори"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Прекар"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index 76dfc0d9..5db8d6d 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"വലുതാക്കുക"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ചെറുതാക്കുക"</string>
     <string name="close_button_text" msgid="2913281996024033299">"അടയ്ക്കുക"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"മടങ്ങുക"</string>
+    <string name="handle_text" msgid="1766582106752184456">"ഹാൻഡിൽ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index a8bd85e..be02be1 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Томруулах"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Багасгах"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Хаах"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Буцах"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Бариул"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index 5874812..779cf5c 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"मोठे करा"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"लहान करा"</string>
     <string name="close_button_text" msgid="2913281996024033299">"बंद करा"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"मागे जा"</string>
+    <string name="handle_text" msgid="1766582106752184456">"हँडल"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index 88270df..85d380e 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimumkan"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimumkan"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Tutup"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Kembali"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Pemegang"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index 9a2d1ba..517098d 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -66,7 +66,7 @@
     <string name="bubbles_user_education_description" msgid="4215862563054175407">"စကားဝိုင်းအသစ်များကို မျောနေသည့် သင်္ကေတများ သို့မဟုတ် ပူဖောင်းကွက်များအဖြစ် မြင်ရပါမည်။ ပူဖောင်းကွက်ကိုဖွင့်ရန် တို့ပါ။ ရွှေ့ရန် ၎င်းကို ဖိဆွဲပါ။"</string>
     <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"ပူဖောင်းကွက်ကို အချိန်မရွေး ထိန်းချုပ်ရန်"</string>
     <string name="bubbles_user_education_manage" msgid="3460756219946517198">"ဤအက်ပ်မှနေ၍ ပူဖောင်းများကို ပိတ်ရန်အတွက် \'စီမံရန်\' ကို တို့ပါ"</string>
-    <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ရပြီ"</string>
+    <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"နားလည်ပြီ"</string>
     <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"လတ်တလော ပူဖောင်းကွက်များ မရှိပါ"</string>
     <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"လတ်တလော ပူဖောင်းကွက်များနှင့် ပိတ်လိုက်သော ပူဖောင်းကွက်များကို ဤနေရာတွင် မြင်ရပါမည်"</string>
     <string name="notification_bubble_title" msgid="6082910224488253378">"ပူဖောင်းဖောက်သံ"</string>
@@ -79,9 +79,11 @@
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ကြည့်ပြီး ပိုမိုလုပ်ဆောင်ပါ"</string>
     <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"မျက်နှာပြင် ခွဲ၍ပြသနိုင်ရန် နောက်အက်ပ်တစ်ခုကို ဖိဆွဲပါ"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"နေရာပြန်ချရန် အက်ပ်အပြင်ဘက်ကို နှစ်ချက်တို့ပါ"</string>
-    <string name="letterbox_education_got_it" msgid="4057634570866051177">"ရပြီ"</string>
+    <string name="letterbox_education_got_it" msgid="4057634570866051177">"နားလည်ပြီ"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"နောက်ထပ်အချက်အလက်များအတွက် ချဲ့နိုင်သည်။"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"ချဲ့ရန်"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ချုံ့ရန်"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ပိတ်ရန်"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"နောက်သို့"</string>
+    <string name="handle_text" msgid="1766582106752184456">"သုံးသူအမည်"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index ec9e635d..0ceee2d 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimer"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimer"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Lukk"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Tilbake"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Håndtak"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index dc7d98d..7ba49e5 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"ठुलो बनाउनुहोस्"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"मिनिमाइज गर्नुहोस्"</string>
     <string name="close_button_text" msgid="2913281996024033299">"बन्द गर्नुहोस्"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"पछाडि"</string>
+    <string name="handle_text" msgid="1766582106752184456">"ह्यान्डल"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index 5bae2a3..dd3ebe4 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximaliseren"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimaliseren"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Sluiten"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Terug"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Gebruikersnaam"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index c5ef4f1..52280a1 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"ବଡ଼ କରନ୍ତୁ"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ଛୋଟ କରନ୍ତୁ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ବନ୍ଦ କରନ୍ତୁ"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"ପଛକୁ ଫେରନ୍ତୁ"</string>
+    <string name="handle_text" msgid="1766582106752184456">"ହେଣ୍ଡେଲ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index f026205..c3056ca 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"ਵੱਡਾ ਕਰੋ"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ਛੋਟਾ ਕਰੋ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ਬੰਦ ਕਰੋ"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"ਪਿੱਛੇ"</string>
+    <string name="handle_text" msgid="1766582106752184456">"ਹੈਂਡਲ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index 22c0c37..56a6fb1 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksymalizuj"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimalizuj"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zamknij"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Wstecz"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Uchwyt"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index 0bbffb3..3cb708d 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Voltar"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Alça"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index 07dd4d7..e5be578 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Anterior"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Indicador"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index 0bbffb3..3cb708d 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Voltar"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Alça"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 0aae6a5..c03e043 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizează"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizează"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Închide"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Înapoi"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Ghidaj"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index f1ff000..b96caf2 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Развернуть"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Свернуть"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Закрыть"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index 39bd260..a1ec3b5 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"විහිදන්න"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"කුඩා කරන්න"</string>
     <string name="close_button_text" msgid="2913281996024033299">"වසන්න"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"ආපසු"</string>
+    <string name="handle_text" msgid="1766582106752184456">"හැඬලය"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index 8859231..e3dafb6 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovať"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimalizovať"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zavrieť"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Späť"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Rukoväť"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index 22fe0f8..2f995e5 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiraj"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimiraj"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zapri"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Nazaj"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Ročica"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index 2a3671b..3d9bde4 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimizo"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizo"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Mbyll"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Pas"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Emërtimi"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index 09471037..b00c4f4 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Увећајте"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Умањите"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Затворите"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Идентификатор"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index 70282a4b..b39fd04 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Utöka"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimera"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Stäng"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Tillbaka"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Handtag"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index 1aab85c..f4d4cee 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Panua"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Punguza"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Funga"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Rudi nyuma"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Ncha"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index befc8eb..6d050c2 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"பெரிதாக்கும்"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"சிறிதாக்கும்"</string>
     <string name="close_button_text" msgid="2913281996024033299">"மூடும்"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"பின்செல்லும்"</string>
+    <string name="handle_text" msgid="1766582106752184456">"ஹேண்டில்"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index a3e21f7..91c411f 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"గరిష్టీకరించండి"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"కుదించండి"</string>
     <string name="close_button_text" msgid="2913281996024033299">"మూసివేయండి"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"వెనుకకు"</string>
+    <string name="handle_text" msgid="1766582106752184456">"హ్యాండిల్"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index 63d3bb8..ee362dc 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"ขยายใหญ่สุด"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ย่อ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ปิด"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"กลับ"</string>
+    <string name="handle_text" msgid="1766582106752184456">"แฮนเดิล"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index 50334f5..d8203656 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"I-maximize"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"I-minimize"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Isara"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Bumalik"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index e1ea0df..025e2e6 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Ekranı Kapla"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Küçült"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Kapat"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Geri"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Herkese açık kullanıcı adı"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index 9e713c7..97bb680 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Збільшити"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Згорнути"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Закрити"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index 596cf4b..883026a 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"بڑا کریں"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"چھوٹا کریں"</string>
     <string name="close_button_text" msgid="2913281996024033299">"بند کریں"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"پیچھے"</string>
+    <string name="handle_text" msgid="1766582106752184456">"ہینڈل"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index 275940a..ce73bd5 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Yoyish"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Kichraytirish"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Yopish"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Orqaga"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 24fb1ca..511db6f 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Phóng to"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Thu nhỏ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Đóng"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Quay lại"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Xử lý"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index e52a7b5..16cbf12 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
     <string name="close_button_text" msgid="2913281996024033299">"关闭"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"返回"</string>
+    <string name="handle_text" msgid="1766582106752184456">"处理"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index eed1e52..5a497d0 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
     <string name="close_button_text" msgid="2913281996024033299">"關閉"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"返去"</string>
+    <string name="handle_text" msgid="1766582106752184456">"控點"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index f9f28ab..2c2ce33 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
     <string name="close_button_text" msgid="2913281996024033299">"關閉"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"返回"</string>
+    <string name="handle_text" msgid="1766582106752184456">"控點"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index ddb6a4c..59d87a0 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -19,7 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="pip_phone_close" msgid="5783752637260411309">"Vala"</string>
     <string name="pip_phone_expand" msgid="2579292903468287504">"Nweba"</string>
-    <string name="pip_phone_settings" msgid="5468987116750491918">"Izilungiselelo"</string>
+    <string name="pip_phone_settings" msgid="5468987116750491918">"Amasethingi"</string>
     <string name="pip_phone_enter_split" msgid="7042877263880641911">"Faka ukuhlukanisa isikrini"</string>
     <string name="pip_menu_title" msgid="5393619322111827096">"Imenyu"</string>
     <string name="pip_notification_title" msgid="1347104727641353453">"U-<xliff:g id="NAME">%s</xliff:g> ungaphakathi kwesithombe esiphakathi kwesithombe"</string>
@@ -84,4 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Khulisa"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Nciphisa"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Vala"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Emuva"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Isibambo"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 0bc7085..3ee20ea 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -321,4 +321,21 @@
 
     <!-- The smaller size of the dismiss target (shrinks when something is in the target). -->
     <dimen name="floating_dismiss_circle_small">120dp</dimen>
+
+    <!-- The thickness of shadows of a window that has focus in DIP. -->
+    <dimen name="freeform_decor_shadow_focused_thickness">20dp</dimen>
+
+    <!-- The thickness of shadows of a window that doesn't have focus in DIP. -->
+    <dimen name="freeform_decor_shadow_unfocused_thickness">5dp</dimen>
+
+    <!-- Height of button (32dp)  + 2 * margin (5dp each). -->
+    <dimen name="freeform_decor_caption_height">42dp</dimen>
+
+    <!-- Width of buttons (64dp) + handle (128dp) + padding (24dp total). -->
+    <dimen name="freeform_decor_caption_width">216dp</dimen>
+
+    <dimen name="freeform_resize_handle">30dp</dimen>
+
+    <dimen name="freeform_resize_corner">44dp</dimen>
+
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 938189f..cbcd949 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -75,13 +75,14 @@
     private static final String TAG = "BackAnimationController";
     private static final int SETTING_VALUE_OFF = 0;
     private static final int SETTING_VALUE_ON = 1;
-    private static final String PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP =
-            "persist.wm.debug.predictive_back_progress_threshold";
     public static final boolean IS_ENABLED =
             SystemProperties.getInt("persist.wm.debug.predictive_back",
-                    SETTING_VALUE_ON) != SETTING_VALUE_OFF;
-    private static final int PROGRESS_THRESHOLD = SystemProperties
-            .getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1);
+                    SETTING_VALUE_ON) == SETTING_VALUE_ON;
+    /** Flag for U animation features */
+    public static boolean IS_U_ANIMATION_ENABLED =
+            SystemProperties.getInt("persist.wm.debug.predictive_back_anim",
+                    SETTING_VALUE_OFF) == SETTING_VALUE_ON;
+    /** Predictive back animation developer option */
     private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
     // TODO (b/241808055) Find a appropriate time to remove during refactor
     private static final boolean USE_TRANSITION =
@@ -114,7 +115,6 @@
     @Nullable
     private IOnBackInvokedCallback mBackToLauncherCallback;
     private float mTriggerThreshold;
-    private float mProgressThreshold;
     private final Runnable mResetTransitionRunnable = () -> {
         finishAnimation();
         mTransitionInProgress = false;
@@ -125,7 +125,6 @@
     private IBackNaviAnimationController mBackAnimationController;
     private BackAnimationAdaptor mBackAnimationAdaptor;
 
-    private boolean mWaitingAnimationStart;
     private final TouchTracker mTouchTracker = new TouchTracker();
     private final CachingBackDispatcher mCachingBackDispatcher = new CachingBackDispatcher();
 
@@ -148,44 +147,6 @@
     };
 
     /**
-     * Helper class to record the touch location for gesture start and latest.
-     */
-    private static class TouchTracker {
-        /**
-         * Location of the latest touch event
-         */
-        private float mLatestTouchX;
-        private float mLatestTouchY;
-        private int mSwipeEdge;
-
-        /**
-         * Location of the initial touch event of the back gesture.
-         */
-        private float mInitTouchX;
-        private float mInitTouchY;
-
-        void update(float touchX, float touchY, int swipeEdge) {
-            mLatestTouchX = touchX;
-            mLatestTouchY = touchY;
-            mSwipeEdge = swipeEdge;
-        }
-
-        void setGestureStartLocation(float touchX, float touchY) {
-            mInitTouchX = touchX;
-            mInitTouchY = touchY;
-        }
-
-        int getDeltaFromGestureStart(float touchX) {
-            return Math.round(touchX - mInitTouchX);
-        }
-
-        void reset() {
-            mInitTouchX = 0;
-            mInitTouchY = 0;
-        }
-    }
-
-    /**
      * Cache the temporary callback and trigger result if gesture was finish before received
      * BackAnimationRunner#onAnimationStart/cancel, so there can continue play the animation.
      */
@@ -212,15 +173,11 @@
             boolean consumed = false;
             if (mWaitingAnimation && mOnBackCallback != null) {
                 if (mTriggerBack) {
-                    final BackEvent backFinish = new BackEvent(
-                            mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 1,
-                            mTouchTracker.mSwipeEdge, mAnimationTarget);
+                    final BackEvent backFinish = mTouchTracker.createProgressEvent(1);
                     dispatchOnBackProgressed(mBackToLauncherCallback, backFinish);
                     dispatchOnBackInvoked(mOnBackCallback);
                 } else {
-                    final BackEvent backFinish = new BackEvent(
-                            mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 0,
-                            mTouchTracker.mSwipeEdge, mAnimationTarget);
+                    final BackEvent backFinish = mTouchTracker.createProgressEvent(0);
                     dispatchOnBackProgressed(mBackToLauncherCallback, backFinish);
                     dispatchOnBackCancelled(mOnBackCallback);
                 }
@@ -263,6 +220,11 @@
         shellInit.addInitCallback(this::onInit, this);
     }
 
+    @VisibleForTesting
+    void setEnableUAnimation(boolean enable) {
+        IS_U_ANIMATION_ENABLED = enable;
+    }
+
     private void onInit() {
         setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
         mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
@@ -404,7 +366,7 @@
             return;
         }
 
-        mTouchTracker.update(touchX, touchY, swipeEdge);
+        mTouchTracker.update(touchX, touchY);
         if (keyAction == MotionEvent.ACTION_DOWN) {
             if (!mBackGestureStarted) {
                 mShouldStartOnNextMoveEvent = true;
@@ -414,7 +376,7 @@
                 // Let the animation initialized here to make sure the onPointerDownOutsideFocus
                 // could be happened when ACTION_DOWN, it may change the current focus that we
                 // would access it when startBackNavigation.
-                onGestureStarted(touchX, touchY);
+                onGestureStarted(touchX, touchY, swipeEdge);
                 mShouldStartOnNextMoveEvent = false;
             }
             onMove(touchX, touchY, swipeEdge);
@@ -428,14 +390,14 @@
         }
     }
 
-    private void onGestureStarted(float touchX, float touchY) {
+    private void onGestureStarted(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) {
         ProtoLog.d(WM_SHELL_BACK_PREVIEW, "initAnimation mMotionStarted=%b", mBackGestureStarted);
         if (mBackGestureStarted || mBackNavigationInfo != null) {
             Log.e(TAG, "Animation is being initialized but is already started.");
             finishAnimation();
         }
 
-        mTouchTracker.setGestureStartLocation(touchX, touchY);
+        mTouchTracker.setGestureStartLocation(touchX, touchY, swipeEdge);
         mBackGestureStarted = true;
 
         try {
@@ -464,6 +426,7 @@
                 displayTargetScreenshot(hardwareBuffer,
                         backNavigationInfo.getTaskWindowConfiguration());
             }
+            targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
             mTransaction.apply();
         } else if (dispatchToLauncher) {
             targetCallback = mBackToLauncherCallback;
@@ -474,7 +437,10 @@
             targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
         }
         if (!USE_TRANSITION || !dispatchToLauncher) {
-            dispatchOnBackStarted(targetCallback);
+            dispatchOnBackStarted(
+                    targetCallback,
+                    mTouchTracker.createStartEvent(
+                            mBackNavigationInfo.getDepartingAnimationTarget()));
         }
     }
 
@@ -514,29 +480,15 @@
         if (!mBackGestureStarted || mBackNavigationInfo == null) {
             return;
         }
-        int deltaX = mTouchTracker.getDeltaFromGestureStart(touchX);
-        float progressThreshold = PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold;
-        float progress = Math.min(Math.max(Math.abs(deltaX) / progressThreshold, 0), 1);
-        if (USE_TRANSITION) {
-            if (mBackAnimationController != null && mAnimationTarget != null) {
-                final BackEvent backEvent = new BackEvent(
-                        touchX, touchY, progress, swipeEdge, mAnimationTarget);
+        final BackEvent backEvent = mTouchTracker.createProgressEvent();
+        if (USE_TRANSITION && mBackAnimationController != null && mAnimationTarget != null) {
                 dispatchOnBackProgressed(mBackToLauncherCallback, backEvent);
-            }
-        } else {
+        } else if (mEnableAnimations.get()) {
             int backType = mBackNavigationInfo.getType();
-            RemoteAnimationTarget animationTarget =
-                    mBackNavigationInfo.getDepartingAnimationTarget();
-
-            BackEvent backEvent = new BackEvent(
-                    touchX, touchY, progress, swipeEdge, animationTarget);
-            IOnBackInvokedCallback targetCallback = null;
+            IOnBackInvokedCallback targetCallback;
             if (shouldDispatchToLauncher(backType)) {
                 targetCallback = mBackToLauncherCallback;
-            } else if (backType == BackNavigationInfo.TYPE_CROSS_TASK
-                    || backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) {
-                // TODO(208427216) Run the actual animation
-            } else if (backType == BackNavigationInfo.TYPE_CALLBACK) {
+            } else {
                 targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
             }
             dispatchOnBackProgressed(targetCallback, backEvent);
@@ -620,18 +572,21 @@
                 || mBackNavigationInfo.getDepartingAnimationTarget() != null);
     }
 
-    private static void dispatchOnBackStarted(IOnBackInvokedCallback callback) {
+    private void dispatchOnBackStarted(IOnBackInvokedCallback callback,
+            BackEvent backEvent) {
         if (callback == null) {
             return;
         }
         try {
-            callback.onBackStarted();
+            if (shouldDispatchAnimation(callback)) {
+                callback.onBackStarted(backEvent);
+            }
         } catch (RemoteException e) {
             Log.e(TAG, "dispatchOnBackStarted error: ", e);
         }
     }
 
-    private static void dispatchOnBackInvoked(IOnBackInvokedCallback callback) {
+    private void dispatchOnBackInvoked(IOnBackInvokedCallback callback) {
         if (callback == null) {
             return;
         }
@@ -642,29 +597,38 @@
         }
     }
 
-    private static void dispatchOnBackCancelled(IOnBackInvokedCallback callback) {
+    private void dispatchOnBackCancelled(IOnBackInvokedCallback callback) {
         if (callback == null) {
             return;
         }
         try {
-            callback.onBackCancelled();
+            if (shouldDispatchAnimation(callback)) {
+                callback.onBackCancelled();
+            }
         } catch (RemoteException e) {
             Log.e(TAG, "dispatchOnBackCancelled error: ", e);
         }
     }
 
-    private static void dispatchOnBackProgressed(IOnBackInvokedCallback callback,
+    private void dispatchOnBackProgressed(IOnBackInvokedCallback callback,
             BackEvent backEvent) {
         if (callback == null) {
             return;
         }
         try {
-            callback.onBackProgressed(backEvent);
+            if (shouldDispatchAnimation(callback)) {
+                callback.onBackProgressed(backEvent);
+            }
         } catch (RemoteException e) {
             Log.e(TAG, "dispatchOnBackProgressed error: ", e);
         }
     }
 
+    private boolean shouldDispatchAnimation(IOnBackInvokedCallback callback) {
+        return (IS_U_ANIMATION_ENABLED || callback == mBackToLauncherCallback)
+                && mEnableAnimations.get();
+    }
+
     /**
      * Sets to true when the back gesture has passed the triggering threshold, false otherwise.
      */
@@ -673,10 +637,11 @@
             return;
         }
         mTriggerBack = triggerBack;
+        mTouchTracker.setTriggerBack(triggerBack);
     }
 
     private void setSwipeThresholds(float triggerThreshold, float progressThreshold) {
-        mProgressThreshold = progressThreshold;
+        mTouchTracker.setProgressThreshold(progressThreshold);
         mTriggerThreshold = triggerThreshold;
     }
 
@@ -686,6 +651,7 @@
         BackNavigationInfo backNavigationInfo = mBackNavigationInfo;
         boolean triggerBack = mTriggerBack;
         mBackNavigationInfo = null;
+        mAnimationTarget = null;
         mTriggerBack = false;
         mShouldStartOnNextMoveEvent = false;
         if (backNavigationInfo == null) {
@@ -762,17 +728,9 @@
                             tx.apply();
                         }
                     }
-                    // TODO animation target should be passed at onBackStarted
-                    dispatchOnBackStarted(mBackToLauncherCallback);
-                    // TODO This is Workaround for LauncherBackAnimationController, there will need
-                    //  to dispatch onBackProgressed twice(startBack & updateBackProgress) to
-                    //  initialize the animation data, for now that would happen when onMove
-                    //  called, but there will no expected animation if the down -> up gesture
-                    //  happen very fast which ACTION_MOVE only happen once.
-                    final BackEvent backInit = new BackEvent(
-                            mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 0,
-                            mTouchTracker.mSwipeEdge, mAnimationTarget);
-                    dispatchOnBackProgressed(mBackToLauncherCallback, backInit);
+                    dispatchOnBackStarted(mBackToLauncherCallback,
+                            mTouchTracker.createStartEvent(mAnimationTarget));
+                    final BackEvent backInit = mTouchTracker.createProgressEvent();
                     if (!mCachingBackDispatcher.consume()) {
                         dispatchOnBackProgressed(mBackToLauncherCallback, backInit);
                     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
new file mode 100644
index 0000000..ccfac65
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -0,0 +1,119 @@
+/*
+ * 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.wm.shell.back;
+
+import android.os.SystemProperties;
+import android.view.RemoteAnimationTarget;
+import android.window.BackEvent;
+
+/**
+ * Helper class to record the touch location for gesture and generate back events.
+ */
+class TouchTracker {
+    private static final String PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP =
+            "persist.wm.debug.predictive_back_progress_threshold";
+    private static final int PROGRESS_THRESHOLD = SystemProperties
+            .getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1);
+    private float mProgressThreshold;
+    /**
+     * Location of the latest touch event
+     */
+    private float mLatestTouchX;
+    private float mLatestTouchY;
+    private boolean mTriggerBack;
+
+    /**
+     * Location of the initial touch event of the back gesture.
+     */
+    private float mInitTouchX;
+    private float mInitTouchY;
+    private float mStartThresholdX;
+    private int mSwipeEdge;
+    private boolean mCancelled;
+
+    void update(float touchX, float touchY) {
+        /**
+         * If back was previously cancelled but the user has started swiping in the forward
+         * direction again, restart back.
+         */
+        if (mCancelled && ((touchX > mLatestTouchX && mSwipeEdge == BackEvent.EDGE_LEFT)
+                || touchX < mLatestTouchX && mSwipeEdge == BackEvent.EDGE_RIGHT)) {
+            mCancelled = false;
+            mStartThresholdX = touchX;
+        }
+        mLatestTouchX = touchX;
+        mLatestTouchY = touchY;
+    }
+
+    void setTriggerBack(boolean triggerBack) {
+        if (mTriggerBack != triggerBack && !triggerBack) {
+            mCancelled = true;
+        }
+        mTriggerBack = triggerBack;
+    }
+
+    void setGestureStartLocation(float touchX, float touchY, int swipeEdge) {
+        mInitTouchX = touchX;
+        mInitTouchY = touchY;
+        mSwipeEdge = swipeEdge;
+        mStartThresholdX = mInitTouchX;
+    }
+
+    void reset() {
+        mInitTouchX = 0;
+        mInitTouchY = 0;
+        mStartThresholdX = 0;
+        mCancelled = false;
+        mTriggerBack = false;
+        mSwipeEdge = BackEvent.EDGE_LEFT;
+    }
+
+    BackEvent createStartEvent(RemoteAnimationTarget target) {
+        return new BackEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target);
+    }
+
+    BackEvent createProgressEvent() {
+        float progressThreshold = PROGRESS_THRESHOLD >= 0
+                ? PROGRESS_THRESHOLD : mProgressThreshold;
+        progressThreshold = progressThreshold == 0 ? 1 : progressThreshold;
+        float progress = 0;
+        // Progress is always 0 when back is cancelled and not restarted.
+        if (!mCancelled) {
+            // If back is committed, progress is the distance between the last and first touch
+            // point, divided by the max drag distance. Otherwise, it's the distance between
+            // the last touch point and the starting threshold, divided by max drag distance.
+            // The starting threshold is initially the first touch location, and updated to
+            // the location everytime back is restarted after being cancelled.
+            float startX = mTriggerBack ? mInitTouchX : mStartThresholdX;
+            float deltaX = Math.max(
+                    mSwipeEdge == BackEvent.EDGE_LEFT
+                            ? mLatestTouchX - startX
+                            : startX - mLatestTouchX,
+                    0);
+            progress = Math.min(Math.max(deltaX / progressThreshold, 0), 1);
+        }
+        return createProgressEvent(progress);
+    }
+
+    BackEvent createProgressEvent(float progress) {
+        return new BackEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
+    }
+
+    public void setProgressThreshold(float progressThreshold) {
+        mProgressThreshold = progressThreshold;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
index d6803e8..d3a9a67 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
@@ -52,7 +52,7 @@
             userBadgedAppIcon = new CircularRingDrawable(userBadgedAppIcon);
         }
         Bitmap userBadgedBitmap = createIconBitmap(
-                userBadgedAppIcon, 1, BITMAP_GENERATION_MODE_WITH_SHADOW);
+                userBadgedAppIcon, 1, MODE_WITH_SHADOW);
         return createIconBitmap(userBadgedBitmap);
     }
 
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 93413db..725b205 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
@@ -28,10 +28,6 @@
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_BOTTOM;
-import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_LEFT;
-import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_NONE;
-import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_RIGHT;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_BLOCKED;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_GROUP_CANCELLED;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_INVALID_INTENT;
@@ -41,6 +37,7 @@
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED;
+import static com.android.wm.shell.floating.FloatingTasksController.SHOW_FLOATING_TASKS_AS_BUBBLES;
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
@@ -59,10 +56,8 @@
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.graphics.PixelFormat;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Binder;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -126,18 +121,6 @@
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
 
-    // TODO(b/173386799) keep in sync with Launcher3, not hooked up to anything
-    public static final String EXTRA_TASKBAR_CREATED = "taskbarCreated";
-    public static final String EXTRA_BUBBLE_OVERFLOW_OPENED = "bubbleOverflowOpened";
-    public static final String EXTRA_TASKBAR_VISIBLE = "taskbarVisible";
-    public static final String EXTRA_TASKBAR_POSITION = "taskbarPosition";
-    public static final String EXTRA_TASKBAR_ICON_SIZE = "taskbarIconSize";
-    public static final String EXTRA_TASKBAR_BUBBLE_XY = "taskbarBubbleXY";
-    public static final String EXTRA_TASKBAR_SIZE = "taskbarSize";
-    public static final String LEFT_POSITION = "Left";
-    public static final String RIGHT_POSITION = "Right";
-    public static final String BOTTOM_POSITION = "Bottom";
-
     // Should match with PhoneWindowManager
     private static final String SYSTEM_DIALOG_REASON_KEY = "reason";
     private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
@@ -470,52 +453,6 @@
         mBubbleData.setExpanded(true);
     }
 
-    /** Called when any taskbar state changes (e.g. visibility, position, sizes). */
-    private void onTaskbarChanged(Bundle b) {
-        if (b == null) {
-            return;
-        }
-        boolean isVisible = b.getBoolean(EXTRA_TASKBAR_VISIBLE, false /* default */);
-        String position = b.getString(EXTRA_TASKBAR_POSITION, RIGHT_POSITION /* default */);
-        @BubblePositioner.TaskbarPosition int taskbarPosition = TASKBAR_POSITION_NONE;
-        switch (position) {
-            case LEFT_POSITION:
-                taskbarPosition = TASKBAR_POSITION_LEFT;
-                break;
-            case RIGHT_POSITION:
-                taskbarPosition = TASKBAR_POSITION_RIGHT;
-                break;
-            case BOTTOM_POSITION:
-                taskbarPosition = TASKBAR_POSITION_BOTTOM;
-                break;
-        }
-        int[] itemPosition = b.getIntArray(EXTRA_TASKBAR_BUBBLE_XY);
-        int iconSize = b.getInt(EXTRA_TASKBAR_ICON_SIZE);
-        int taskbarSize = b.getInt(EXTRA_TASKBAR_SIZE);
-        Log.w(TAG, "onTaskbarChanged:"
-                + " isVisible: " + isVisible
-                + " position: " + position
-                + " itemPosition: " + itemPosition[0] + "," + itemPosition[1]
-                + " iconSize: " + iconSize);
-        PointF point = new PointF(itemPosition[0], itemPosition[1]);
-        mBubblePositioner.setPinnedLocation(isVisible ? point : null);
-        mBubblePositioner.updateForTaskbar(iconSize, taskbarPosition, isVisible, taskbarSize);
-        if (mStackView != null) {
-            if (isVisible && b.getBoolean(EXTRA_TASKBAR_CREATED, false /* default */)) {
-                // If taskbar was created, add and remove the window so that bubbles display on top
-                removeFromWindowManagerMaybe();
-                addToWindowManagerMaybe();
-            }
-            mStackView.updateStackPosition();
-            mBubbleIconFactory = new BubbleIconFactory(mContext);
-            mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext);
-            mStackView.onDisplaySizeChanged();
-        }
-        if (b.getBoolean(EXTRA_BUBBLE_OVERFLOW_OPENED, false)) {
-            openBubbleOverflow();
-        }
-    }
-
     /**
      * Called when the status bar has become visible or invisible (either permanently or
      * temporarily).
@@ -654,6 +591,11 @@
             }
             mStackView.setUnbubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
         }
+        if (SHOW_FLOATING_TASKS_AS_BUBBLES && mBubblePositioner.isLargeScreen()) {
+            mBubblePositioner.setUsePinnedLocation(true);
+        } else {
+            mBubblePositioner.setUsePinnedLocation(false);
+        }
 
         addToWindowManagerMaybe();
     }
@@ -1732,13 +1674,6 @@
         }
 
         @Override
-        public void onTaskbarChanged(Bundle b) {
-            mMainExecutor.execute(() -> {
-                BubbleController.this.onTaskbarChanged(b);
-            });
-        }
-
-        @Override
         public boolean handleDismissalInterception(BubbleEntry entry,
                 @Nullable List<BubbleEntry> children, IntConsumer removeCallback,
                 Executor callbackExecutor) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
index 5dab8a0..4ded3ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
@@ -79,6 +79,6 @@
                 true /* shrinkNonAdaptiveIcons */,
                 null /* outscale */,
                 outScale);
-        return createIconBitmap(icon, outScale[0], BITMAP_GENERATION_MODE_WITH_SHADOW);
+        return createIconBitmap(icon, outScale[0], MODE_WITH_SHADOW);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index dbad5df..07c5852 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -713,6 +713,9 @@
      * is being shown.
      */
     public PointF getDefaultStartPosition() {
+        if (mPinLocation != null) {
+            return mPinLocation;
+        }
         // Start on the left if we're in LTR, right otherwise.
         final boolean startOnLeft =
                 mContext.getResources().getConfiguration().getLayoutDirection()
@@ -766,11 +769,18 @@
     }
 
     /**
-     * In some situations bubbles will be pinned to a specific onscreen location. This sets the
-     * location to anchor the stack to.
+     * In some situations bubbles will be pinned to a specific onscreen location. This sets whether
+     * bubbles should be pinned or not.
      */
-    public void setPinnedLocation(PointF point) {
-        mPinLocation = point;
+    public void setUsePinnedLocation(boolean usePinnedLocation) {
+        if (usePinnedLocation) {
+            mShowingInTaskbar = true;
+            mPinLocation = new PointF(mPositionRect.right - mBubbleSize,
+                    mPositionRect.bottom - mBubbleSize);
+        } else {
+            mPinLocation = null;
+            mShowingInTaskbar = false;
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index be100bb..6efad09 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -613,16 +613,11 @@
                 mBubbleContainer.setActiveController(mStackAnimationController);
                 hideFlyoutImmediate();
 
-                if (mPositioner.showingInTaskbar()) {
-                    // In taskbar, the stack isn't draggable so we shouldn't dispatch touch events.
-                    mMagnetizedObject = null;
-                } else {
-                    // Save the magnetized stack so we can dispatch touch events to it.
-                    mMagnetizedObject = mStackAnimationController.getMagnetizedStack();
-                    mMagnetizedObject.clearAllTargets();
-                    mMagnetizedObject.addTarget(mMagneticTarget);
-                    mMagnetizedObject.setMagnetListener(mStackMagnetListener);
-                }
+                // Save the magnetized stack so we can dispatch touch events to it.
+                mMagnetizedObject = mStackAnimationController.getMagnetizedStack();
+                mMagnetizedObject.clearAllTargets();
+                mMagnetizedObject.addTarget(mMagneticTarget);
+                mMagnetizedObject.setMagnetListener(mStackMagnetListener);
 
                 mIsDraggingStack = true;
 
@@ -641,10 +636,7 @@
         public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
                 float viewInitialY, float dx, float dy) {
             // If we're expanding or collapsing, ignore all touch events.
-            if (mIsExpansionAnimating
-                    // Also ignore events if we shouldn't be draggable.
-                    || (mPositioner.showingInTaskbar() && !mIsExpanded)
-                    || mShowedUserEducationInTouchListenerActive) {
+            if (mIsExpansionAnimating || mShowedUserEducationInTouchListenerActive) {
                 return;
             }
 
@@ -661,7 +653,7 @@
             // bubble since it's stuck to the target.
             if (!passEventToMagnetizedObject(ev)) {
                 updateBubbleShadows(true /* showForAllBubbles */);
-                if (mBubbleData.isExpanded() || mPositioner.showingInTaskbar()) {
+                if (mBubbleData.isExpanded()) {
                     mExpandedAnimationController.dragBubbleOut(
                             v, viewInitialX + dx, viewInitialY + dy);
                 } else {
@@ -678,9 +670,7 @@
         public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
                 float viewInitialY, float dx, float dy, float velX, float velY) {
             // If we're expanding or collapsing, ignore all touch events.
-            if (mIsExpansionAnimating
-                    // Also ignore events if we shouldn't be draggable.
-                    || (mPositioner.showingInTaskbar() && !mIsExpanded)) {
+            if (mIsExpansionAnimating) {
                 return;
             }
             if (mShowedUserEducationInTouchListenerActive) {
@@ -696,6 +686,8 @@
 
                     // Re-show the expanded view if we hid it.
                     showExpandedViewIfNeeded();
+                } else if (mPositioner.showingInTaskbar()) {
+                    mStackAnimationController.snapStackBack();
                 } else {
                     // Fling the stack to the edge, and save whether or not it's going to end up on
                     // the left side of the screen.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index b3104b5..7f891ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -23,7 +23,6 @@
 
 import android.app.NotificationChannel;
 import android.content.pm.UserInfo;
-import android.os.Bundle;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.RankingMap;
@@ -114,9 +113,6 @@
     @Nullable
     Bubble getBubbleWithShortcutId(String shortcutId);
 
-    /** Called for any taskbar changes. */
-    void onTaskbarChanged(Bundle b);
-
     /**
      * We intercept notification entries (including group summaries) dismissed by the user when
      * there is an active bubble associated with it. We do this so that developers can still
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 961722b..0ee0ea6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -417,6 +417,17 @@
     }
 
     /**
+     * Snaps the stack back to the previous resting position.
+     */
+    public void snapStackBack() {
+        if (mLayout == null) {
+            return;
+        }
+        PointF p = getStackPositionAlongNearestHorizontalEdge();
+        springStackAfterFling(p.x, p.y);
+    }
+
+    /**
      * Where the stack would be if it were snapped to the nearest horizontal edge (left or right).
      */
     public PointF getStackPositionAlongNearestHorizontalEdge() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index 44a467f..cbd544c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -18,9 +18,21 @@
 
 import com.android.wm.shell.common.annotations.ExternalThread;
 
+import java.util.concurrent.Executor;
+
 /**
  * Interface to interact with desktop mode feature in shell.
  */
 @ExternalThread
 public interface DesktopMode {
+
+    /**
+     * Adds a listener to find out about changes in the visibility of freeform tasks.
+     *
+     * @param listener the listener to add.
+     * @param callbackExecutor the executor to call the listener on.
+     */
+    void addListener(DesktopModeTaskRepository.VisibleTasksListener listener,
+            Executor callbackExecutor);
+
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index b96facf..34ff6d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
@@ -60,6 +61,7 @@
 
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.concurrent.Executor;
 
 /**
  * Handles windowing changes when desktop mode system setting changes
@@ -132,6 +134,17 @@
         return new IDesktopModeImpl(this);
     }
 
+    /**
+     * Adds a listener to find out about changes in the visibility of freeform tasks.
+     *
+     * @param listener the listener to add.
+     * @param callbackExecutor the executor to call the listener on.
+     */
+    public void addListener(DesktopModeTaskRepository.VisibleTasksListener listener,
+            Executor callbackExecutor) {
+        mDesktopModeTaskRepository.addVisibleTasksListener(listener, callbackExecutor);
+    }
+
     @VisibleForTesting
     void updateDesktopModeActive(boolean active) {
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeActive: active=%s", active);
@@ -181,7 +194,18 @@
     /**
      * Show apps on desktop
      */
-    WindowContainerTransaction showDesktopApps() {
+    void showDesktopApps() {
+        WindowContainerTransaction wct = bringDesktopAppsToFront();
+
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            mTransitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */);
+        } else {
+            mShellTaskOrganizer.applyTransaction(wct);
+        }
+    }
+
+    @NonNull
+    private WindowContainerTransaction bringDesktopAppsToFront() {
         ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
         ArrayList<RunningTaskInfo> taskInfos = new ArrayList<>();
@@ -197,11 +221,6 @@
         for (RunningTaskInfo task : taskInfos) {
             wct.reorder(task.token, true);
         }
-
-        if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
-            mShellTaskOrganizer.applyTransaction(wct);
-        }
-
         return wct;
     }
 
@@ -237,17 +256,29 @@
     @Override
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
             @NonNull TransitionRequestInfo request) {
-
-        // Only do anything if we are in desktop mode and opening a task/app
-        if (!DesktopModeStatus.isActive(mContext) || request.getType() != TRANSIT_OPEN) {
+        // Only do anything if we are in desktop mode and opening a task/app in freeform
+        if (!DesktopModeStatus.isActive(mContext)) {
+            ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+                    "skip shell transition request: desktop mode not active");
             return null;
         }
+        if (request.getType() != TRANSIT_OPEN) {
+            ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+                    "skip shell transition request: only supports TRANSIT_OPEN");
+            return null;
+        }
+        if (request.getTriggerTask() == null
+                || request.getTriggerTask().getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+            ProtoLog.d(WM_SHELL_DESKTOP_MODE, "skip shell transition request: not freeform task");
+            return null;
+        }
+        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "handle shell transition request: %s", request);
 
         WindowContainerTransaction wct = mTransitions.dispatchRequest(transition, request, this);
         if (wct == null) {
             wct = new WindowContainerTransaction();
         }
-        wct.merge(showDesktopApps(), true /* transfer */);
+        wct.merge(bringDesktopAppsToFront(), true /* transfer */);
         wct.reorder(request.getTriggerTask().token, true /* onTop */);
 
         return wct;
@@ -293,7 +324,14 @@
      */
     @ExternalThread
     private final class DesktopModeImpl implements DesktopMode {
-        // Do nothing
+
+        @Override
+        public void addListener(DesktopModeTaskRepository.VisibleTasksListener listener,
+                Executor callbackExecutor) {
+            mMainExecutor.execute(() -> {
+                DesktopModeController.this.addListener(listener, callbackExecutor);
+            });
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 988601c..c91d54a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -16,7 +16,9 @@
 
 package com.android.wm.shell.desktopmode
 
+import android.util.ArrayMap
 import android.util.ArraySet
+import java.util.concurrent.Executor
 
 /**
  * Keeps track of task data related to desktop mode.
@@ -30,20 +32,39 @@
      * Task gets removed from this list when it vanishes. Or when desktop mode is turned off.
      */
     private val activeTasks = ArraySet<Int>()
-    private val listeners = ArraySet<Listener>()
+    private val visibleTasks = ArraySet<Int>()
+    private val activeTasksListeners = ArraySet<ActiveTasksListener>()
+    // Track visible tasks separately because a task may be part of the desktop but not visible.
+    private val visibleTasksListeners = ArrayMap<VisibleTasksListener, Executor>()
 
     /**
-     * Add a [Listener] to be notified of updates to the repository.
+     * Add a [ActiveTasksListener] to be notified of updates to active tasks in the repository.
      */
-    fun addListener(listener: Listener) {
-        listeners.add(listener)
+    fun addActiveTaskListener(activeTasksListener: ActiveTasksListener) {
+        activeTasksListeners.add(activeTasksListener)
     }
 
     /**
-     * Remove a previously registered [Listener]
+     * Add a [VisibleTasksListener] to be notified when freeform tasks are visible or not.
      */
-    fun removeListener(listener: Listener) {
-        listeners.remove(listener)
+    fun addVisibleTasksListener(visibleTasksListener: VisibleTasksListener, executor: Executor) {
+        visibleTasksListeners.put(visibleTasksListener, executor)
+        executor.execute(
+                Runnable { visibleTasksListener.onVisibilityChanged(visibleTasks.size > 0) })
+    }
+
+    /**
+     * Remove a previously registered [ActiveTasksListener]
+     */
+    fun removeActiveTasksListener(activeTasksListener: ActiveTasksListener) {
+        activeTasksListeners.remove(activeTasksListener)
+    }
+
+    /**
+     * Remove a previously registered [VisibleTasksListener]
+     */
+    fun removeVisibleTasksListener(visibleTasksListener: VisibleTasksListener) {
+        visibleTasksListeners.remove(visibleTasksListener)
     }
 
     /**
@@ -52,7 +73,7 @@
     fun addActiveTask(taskId: Int) {
         val added = activeTasks.add(taskId)
         if (added) {
-            listeners.onEach { it.onActiveTasksChanged() }
+            activeTasksListeners.onEach { it.onActiveTasksChanged() }
         }
     }
 
@@ -62,7 +83,7 @@
     fun removeActiveTask(taskId: Int) {
         val removed = activeTasks.remove(taskId)
         if (removed) {
-            listeners.onEach { it.onActiveTasksChanged() }
+            activeTasksListeners.onEach { it.onActiveTasksChanged() }
         }
     }
 
@@ -81,9 +102,43 @@
     }
 
     /**
-     * Defines interface for classes that can listen to changes in repository state.
+     * Updates whether a freeform task with this id is visible or not and notifies listeners.
      */
-    interface Listener {
-        fun onActiveTasksChanged()
+    fun updateVisibleFreeformTasks(taskId: Int, visible: Boolean) {
+        val prevCount: Int = visibleTasks.size
+        if (visible) {
+            visibleTasks.add(taskId)
+        } else {
+            visibleTasks.remove(taskId)
+        }
+        if (prevCount == 0 && visibleTasks.size == 1 ||
+                prevCount > 0 && visibleTasks.size == 0) {
+            for ((listener, executor) in visibleTasksListeners) {
+                executor.execute(
+                        Runnable { listener.onVisibilityChanged(visibleTasks.size > 0) })
+            }
+        }
+    }
+
+    /**
+     * Defines interface for classes that can listen to changes for active tasks in desktop mode.
+     */
+    interface ActiveTasksListener {
+        /**
+         * Called when the active tasks change in desktop mode.
+         */
+        @JvmDefault
+        fun onActiveTasksChanged() {}
+    }
+
+    /**
+     * Defines interface for classes that can listen to changes for visible tasks in desktop mode.
+     */
+    interface VisibleTasksListener {
+        /**
+         * Called when the desktop starts or stops showing freeform tasks.
+         */
+        @JvmDefault
+        fun onVisibilityChanged(hasVisibleFreeformTasks: Boolean) {}
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index f82a346..eaa7158 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -90,6 +90,8 @@
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                     "Adding active freeform task: #%d", taskInfo.taskId);
             mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId));
+            mDesktopModeTaskRepository.ifPresent(
+                    it -> it.updateVisibleFreeformTasks(taskInfo.taskId, true));
         }
     }
 
@@ -103,6 +105,8 @@
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                     "Removing active freeform task: #%d", taskInfo.taskId);
             mDesktopModeTaskRepository.ifPresent(it -> it.removeActiveTask(taskInfo.taskId));
+            mDesktopModeTaskRepository.ifPresent(
+                    it -> it.updateVisibleFreeformTasks(taskInfo.taskId, false));
         }
 
         if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -124,6 +128,8 @@
                         "Adding active freeform task: #%d", taskInfo.taskId);
                 mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId));
             }
+            mDesktopModeTaskRepository.ifPresent(
+                    it -> it.updateVisibleFreeformTasks(taskInfo.taskId, taskInfo.isVisible));
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index afb64c9..43d3f36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -60,7 +60,7 @@
         FloatingContentCoordinator.FloatingContent {
 
     public static final boolean ENABLE_FLING_TO_DISMISS_PIP =
-            SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_pip", true);
+            SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_pip", false);
     private static final String TAG = "PipMotionHelper";
     private static final boolean DEBUG = false;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
index b71cc32..1a6c1d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
@@ -16,7 +16,7 @@
 
 package com.android.wm.shell.recents;
 
-import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
 
 import com.android.wm.shell.recents.IRecentTasksListener;
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
@@ -44,5 +44,5 @@
     /**
      * Gets the set of running tasks.
      */
-    ActivityManager.RunningTaskInfo[] getRunningTasks(int maxNum) = 4;
+    RunningTaskInfo[] getRunningTasks(int maxNum) = 4;
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
index 59f7233..e8f58fe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
@@ -16,7 +16,7 @@
 
 package com.android.wm.shell.recents;
 
-import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
 
 /**
  * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
@@ -31,10 +31,10 @@
     /**
      * Called when a running task appears.
      */
-    void onRunningTaskAppeared(in ActivityManager.RunningTaskInfo taskInfo);
+    void onRunningTaskAppeared(in RunningTaskInfo taskInfo);
 
     /**
      * Called when a running task vanishes.
      */
-    void onRunningTaskVanished(in ActivityManager.RunningTaskInfo taskInfo);
-}
\ No newline at end of file
+    void onRunningTaskVanished(in RunningTaskInfo taskInfo);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 08f3db6..f9172ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -68,7 +68,7 @@
  * Manages the recent task list from the system, caching it as necessary.
  */
 public class RecentTasksController implements TaskStackListenerCallback,
-        RemoteCallable<RecentTasksController>, DesktopModeTaskRepository.Listener {
+        RemoteCallable<RecentTasksController>, DesktopModeTaskRepository.ActiveTasksListener {
     private static final String TAG = RecentTasksController.class.getSimpleName();
 
     private final Context mContext;
@@ -147,7 +147,7 @@
                 this::createExternalInterface, this);
         mShellCommandHandler.addDumpCallback(this::dump, this);
         mTaskStackListener.addListener(this);
-        mDesktopModeTaskRepository.ifPresent(it -> it.addListener(this));
+        mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTaskListener(this));
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 9102bd3..e2ac01f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -207,6 +207,7 @@
     private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
 
     private DefaultMixedHandler mMixedHandler;
+    private final Toast mSplitUnsupportedToast;
 
     private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
             new SplitWindowManager.ParentContainerCallbacks() {
@@ -300,6 +301,8 @@
         mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId));
         transitions.addHandler(this);
         mTaskOrganizer.addFocusListener(this);
+        mSplitUnsupportedToast = Toast.makeText(mContext,
+                R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
     }
 
     @VisibleForTesting
@@ -329,6 +332,8 @@
         mDisplayController.addDisplayWindowListener(this);
         mDisplayLayout = new DisplayLayout();
         transitions.addHandler(this);
+        mSplitUnsupportedToast = Toast.makeText(mContext,
+                R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
     }
 
     public void setMixedHandler(DefaultMixedHandler mixedHandler) {
@@ -470,6 +475,7 @@
                         mMainExecutor.execute(() ->
                                 exitSplitScreen(mMainStage.getChildCount() == 0
                                         ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+                        mSplitUnsupportedToast.show();
                     } else {
                         // Switch the split position if launching as MULTIPLE_TASK failed.
                         if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
@@ -736,6 +742,7 @@
             mMainExecutor.execute(() ->
                     exitSplitScreen(mMainStage.getChildCount() == 0
                             ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+            mSplitUnsupportedToast.show();
         } else {
             mSyncQueue.queue(evictWct);
         }
@@ -2287,13 +2294,11 @@
         @Override
         public void onNoLongerSupportMultiWindow() {
             if (mMainStage.isActive()) {
-                final Toast splitUnsupportedToast = Toast.makeText(mContext,
-                        R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
                 final boolean isMainStage = mMainStageListener == this;
                 if (!ENABLE_SHELL_TRANSITIONS) {
                     StageCoordinator.this.exitSplitScreen(isMainStage ? mMainStage : mSideStage,
                             EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
-                    splitUnsupportedToast.show();
+                    mSplitUnsupportedToast.show();
                     return;
                 }
 
@@ -2302,7 +2307,7 @@
                 prepareExitSplitScreen(stageType, wct);
                 mSplitTransitions.startDismissTransition(wct,StageCoordinator.this, stageType,
                         EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
-                splitUnsupportedToast.show();
+                mSplitUnsupportedToast.show();
             }
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 8cee4f1..6ce981e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -432,7 +432,8 @@
                     final ShapeIconFactory factory = new ShapeIconFactory(
                             SplashscreenContentDrawer.this.mContext,
                             scaledIconDpi, mFinalIconSize);
-                    final Bitmap bitmap = factory.createScaledBitmapWithoutShadow(iconDrawable);
+                    final Bitmap bitmap = factory.createScaledBitmap(iconDrawable,
+                            BaseIconFactory.MODE_DEFAULT);
                     Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                     createIconDrawable(new BitmapDrawable(bitmap), true,
                             mHighResIconProvider.mLoadInDetail);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
index e903897..f209521 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
@@ -33,6 +33,8 @@
     // This class is orientation-agnostic, so we compute both for later use
     public final float topTaskPercent;
     public final float leftTaskPercent;
+    public final float dividerWidthPercent;
+    public final float dividerHeightPercent;
     /**
      * If {@code true}, that means at the time of creation of this object, the
      * split-screened apps were vertically stacked. This is useful in scenarios like
@@ -62,8 +64,12 @@
             appsStackedVertically = false;
         }
 
-        leftTaskPercent = this.leftTopBounds.width() / (float) rightBottomBounds.right;
-        topTaskPercent = this.leftTopBounds.height() / (float) rightBottomBounds.bottom;
+        float totalWidth = rightBottomBounds.right - leftTopBounds.left;
+        float totalHeight = rightBottomBounds.bottom - leftTopBounds.top;
+        leftTaskPercent = leftTopBounds.width() / totalWidth;
+        topTaskPercent = leftTopBounds.height() / totalHeight;
+        dividerWidthPercent = visualDividerBounds.width() / totalWidth;
+        dividerHeightPercent = visualDividerBounds.height() / totalHeight;
     }
 
     public SplitBounds(Parcel parcel) {
@@ -75,6 +81,8 @@
         appsStackedVertically = parcel.readBoolean();
         leftTopTaskId = parcel.readInt();
         rightBottomTaskId = parcel.readInt();
+        dividerWidthPercent = parcel.readInt();
+        dividerHeightPercent = parcel.readInt();
     }
 
     @Override
@@ -87,6 +95,8 @@
         parcel.writeBoolean(appsStackedVertically);
         parcel.writeInt(leftTopTaskId);
         parcel.writeInt(rightBottomTaskId);
+        parcel.writeFloat(dividerWidthPercent);
+        parcel.writeFloat(dividerHeightPercent);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index beace75..9d61c14 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -21,7 +21,6 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Color;
-import android.graphics.Rect;
 import android.graphics.drawable.VectorDrawable;
 import android.os.Handler;
 import android.view.Choreographer;
@@ -43,22 +42,6 @@
  * The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
  */
 public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
-    // The thickness of shadows of a window that has focus in DIP.
-    private static final int DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP = 20;
-    // The thickness of shadows of a window that doesn't have focus in DIP.
-    private static final int DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP = 5;
-
-    // Height of button (32dp)  + 2 * margin (5dp each)
-    private static final int DECOR_CAPTION_HEIGHT_IN_DIP = 42;
-    // Width of buttons (64dp) + handle (128dp) + padding (24dp total)
-    private static final int DECOR_CAPTION_WIDTH_IN_DIP = 216;
-    private static final int RESIZE_HANDLE_IN_DIP = 30;
-    private static final int RESIZE_CORNER_IN_DIP = 44;
-
-    private static final Rect EMPTY_OUTSET = new Rect();
-    private static final Rect RESIZE_HANDLE_OUTSET = new Rect(
-            RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP);
-
     private final Handler mHandler;
     private final Choreographer mChoreographer;
     private final SyncTransactionQueue mSyncQueue;
@@ -69,6 +52,7 @@
 
     private DragResizeInputListener mDragResizeListener;
 
+    private RelayoutParams mRelayoutParams = new RelayoutParams();
     private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
             new WindowDecoration.RelayoutResult<>();
 
@@ -114,19 +98,32 @@
 
     void relayout(ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
-        final int shadowRadiusDp = taskInfo.isFocused
-                ? DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP : DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP;
-        final boolean isFreeform = mTaskInfo.configuration.windowConfiguration.getWindowingMode()
-                == WindowConfiguration.WINDOWING_MODE_FREEFORM;
-        final boolean isDragResizeable = isFreeform && mTaskInfo.isResizeable;
-        final Rect outset = isDragResizeable ? RESIZE_HANDLE_OUTSET : EMPTY_OUTSET;
+        final int shadowRadiusID = taskInfo.isFocused
+                ? R.dimen.freeform_decor_shadow_focused_thickness
+                : R.dimen.freeform_decor_shadow_unfocused_thickness;
+        final boolean isFreeform =
+                taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM;
+        final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
 
         WindowDecorLinearLayout oldRootView = mResult.mRootView;
         final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
         final WindowContainerTransaction wct = new WindowContainerTransaction();
-        relayout(taskInfo, R.layout.caption_window_decoration, oldRootView,
-                DECOR_CAPTION_HEIGHT_IN_DIP, DECOR_CAPTION_WIDTH_IN_DIP, outset, shadowRadiusDp,
-                startT, finishT, wct, mResult);
+
+        int outsetLeftId = R.dimen.freeform_resize_handle;
+        int outsetTopId = R.dimen.freeform_resize_handle;
+        int outsetRightId = R.dimen.freeform_resize_handle;
+        int outsetBottomId = R.dimen.freeform_resize_handle;
+
+        mRelayoutParams.reset();
+        mRelayoutParams.mRunningTaskInfo = taskInfo;
+        mRelayoutParams.mLayoutResId = R.layout.caption_window_decoration;
+        mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
+        mRelayoutParams.mCaptionWidthId = R.dimen.freeform_decor_caption_width;
+        mRelayoutParams.mShadowRadiusId = shadowRadiusID;
+        if (isDragResizeable) {
+            mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
+        }
+        relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
 
         mTaskOrganizer.applyTransaction(wct);
 
@@ -167,10 +164,12 @@
         }
 
         int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop();
-
+        int resize_handle = mResult.mRootView.getResources()
+                .getDimensionPixelSize(R.dimen.freeform_resize_handle);
+        int resize_corner = mResult.mRootView.getResources()
+                .getDimensionPixelSize(R.dimen.freeform_resize_corner);
         mDragResizeListener.setGeometry(
-                mResult.mWidth, mResult.mHeight, (int) (mResult.mDensity * RESIZE_HANDLE_IN_DIP),
-                (int) (mResult.mDensity * RESIZE_CORNER_IN_DIP), touchSlop);
+                mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop);
     }
 
     /**
@@ -218,7 +217,7 @@
         View handle = caption.findViewById(R.id.caption_handle);
         VectorDrawable handleBackground = (VectorDrawable) handle.getBackground();
         handleBackground.setTintList(buttonTintColor);
-        caption.setBackgroundColor(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT);
+        caption.getBackground().setTint(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT);
     }
 
     private void closeDragResizeListener() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index bf863ea..b314163 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -19,11 +19,11 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.util.DisplayMetrics;
 import android.view.Display;
 import android.view.InsetsState;
 import android.view.LayoutInflater;
@@ -91,7 +91,7 @@
     SurfaceControl mTaskBackgroundSurface;
 
     SurfaceControl mCaptionContainerSurface;
-    private CaptionWindowManager mCaptionWindowManager;
+    private WindowlessWindowManager mCaptionWindowManager;
     private SurfaceControlViewHost mViewHost;
 
     private final Rect mCaptionInsetsRect = new Rect();
@@ -142,15 +142,14 @@
      */
     abstract void relayout(RunningTaskInfo taskInfo);
 
-    void relayout(RunningTaskInfo taskInfo, int layoutResId, T rootView, float captionHeightDp,
-            float captionWidthDp, Rect outsetsDp, float shadowRadiusDp,
-            SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
-            WindowContainerTransaction wct, RelayoutResult<T> outResult) {
+    void relayout(RelayoutParams params, SurfaceControl.Transaction startT,
+            SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView,
+            RelayoutResult<T> outResult) {
         outResult.reset();
 
         final Configuration oldTaskConfig = mTaskInfo.getConfiguration();
-        if (taskInfo != null) {
-            mTaskInfo = taskInfo;
+        if (params.mRunningTaskInfo != null) {
+            mTaskInfo = params.mRunningTaskInfo;
         }
 
         if (!mTaskInfo.isVisible) {
@@ -159,7 +158,7 @@
             return;
         }
 
-        if (rootView == null && layoutResId == 0) {
+        if (rootView == null && params.mLayoutResId == 0) {
             throw new IllegalArgumentException("layoutResId and rootView can't both be invalid.");
         }
 
@@ -176,15 +175,15 @@
                 return;
             }
             mDecorWindowContext = mContext.createConfigurationContext(taskConfig);
-            if (layoutResId != 0) {
-                outResult.mRootView =
-                        (T) LayoutInflater.from(mDecorWindowContext).inflate(layoutResId, null);
+            if (params.mLayoutResId != 0) {
+                outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
+                                .inflate(params.mLayoutResId, null);
             }
         }
 
         if (outResult.mRootView == null) {
-            outResult.mRootView =
-                    (T) LayoutInflater.from(mDecorWindowContext).inflate(layoutResId, null);
+            outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
+                            .inflate(params.mLayoutResId , null);
         }
 
         // DecorationContainerSurface
@@ -200,18 +199,19 @@
         }
 
         final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
-        outResult.mDensity = taskConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
-        final int decorContainerOffsetX = -(int) (outsetsDp.left * outResult.mDensity);
-        final int decorContainerOffsetY = -(int) (outsetsDp.top * outResult.mDensity);
+        final Resources resources = mDecorWindowContext.getResources();
+        final int decorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
+        final int decorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
         outResult.mWidth = taskBounds.width()
-                + (int) (outsetsDp.right * outResult.mDensity)
+                + loadDimensionPixelSize(resources, params.mOutsetRightId)
                 - decorContainerOffsetX;
         outResult.mHeight = taskBounds.height()
-                + (int) (outsetsDp.bottom * outResult.mDensity)
+                + loadDimensionPixelSize(resources, params.mOutsetBottomId)
                 - decorContainerOffsetY;
         startT.setPosition(
                         mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY)
-                .setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight)
+                .setWindowCrop(mDecorationContainerSurface,
+                        outResult.mWidth, outResult.mHeight)
                 // TODO(b/244455401): Change the z-order when it's better organized
                 .setLayer(mDecorationContainerSurface, mTaskInfo.numActivities + 1)
                 .show(mDecorationContainerSurface);
@@ -226,12 +226,13 @@
                     .build();
         }
 
-        float shadowRadius = outResult.mDensity * shadowRadiusDp;
+        float shadowRadius = loadDimension(resources, params.mShadowRadiusId);
         int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
         mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
         mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
         mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
-        startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height())
+        startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(),
+                        taskBounds.height())
                 .setShadowRadius(mTaskBackgroundSurface, shadowRadius)
                 .setColor(mTaskBackgroundSurface, mTmpColor)
                 // TODO(b/244455401): Change the z-order when it's better organized
@@ -248,8 +249,8 @@
                     .build();
         }
 
-        final int captionHeight = (int) Math.ceil(captionHeightDp * outResult.mDensity);
-        final int captionWidth = (int) Math.ceil(captionWidthDp * outResult.mDensity);
+        final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
+        final int captionWidth = loadDimensionPixelSize(resources, params.mCaptionWidthId);
 
         //Prevent caption from going offscreen if task is too high up
         final int captionYPos = taskBounds.top <= captionHeight / 2 ? 0 : captionHeight / 2;
@@ -264,8 +265,9 @@
         if (mCaptionWindowManager == null) {
             // Put caption under a container surface because ViewRootImpl sets the destination frame
             // of windowless window layers and BLASTBufferQueue#update() doesn't support offset.
-            mCaptionWindowManager = new CaptionWindowManager(
-                    mTaskInfo.getConfiguration(), mCaptionContainerSurface);
+            mCaptionWindowManager = new WindowlessWindowManager(
+                    mTaskInfo.getConfiguration(), mCaptionContainerSurface,
+                    null /* hostInputToken */);
         }
 
         // Caption view
@@ -289,8 +291,10 @@
 
             // Caption insets
             mCaptionInsetsRect.set(taskBounds);
-            mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight - captionYPos;
-            wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect, CAPTION_INSETS_TYPES);
+            mCaptionInsetsRect.bottom =
+                    mCaptionInsetsRect.top + captionHeight - captionYPos;
+            wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect,
+                    CAPTION_INSETS_TYPES);
         } else {
             startT.hide(mCaptionContainerSurface);
         }
@@ -365,34 +369,67 @@
         releaseViews();
     }
 
+    private static int loadDimensionPixelSize(Resources resources, int resourceId) {
+        if (resourceId == Resources.ID_NULL) {
+            return 0;
+        }
+        return resources.getDimensionPixelSize(resourceId);
+    }
+
+    private static float loadDimension(Resources resources, int resourceId) {
+        if (resourceId == Resources.ID_NULL) {
+            return 0;
+        }
+        return resources.getDimension(resourceId);
+    }
+
+    static class RelayoutParams{
+        RunningTaskInfo mRunningTaskInfo;
+        int mLayoutResId;
+        int mCaptionHeightId;
+        int mCaptionWidthId;
+        int mShadowRadiusId;
+
+        int mOutsetTopId;
+        int mOutsetBottomId;
+        int mOutsetLeftId;
+        int mOutsetRightId;
+
+        void setOutsets(int leftId, int topId, int rightId, int bottomId) {
+            mOutsetLeftId = leftId;
+            mOutsetTopId = topId;
+            mOutsetRightId = rightId;
+            mOutsetBottomId = bottomId;
+        }
+
+        void reset() {
+            mLayoutResId = Resources.ID_NULL;
+            mCaptionHeightId = Resources.ID_NULL;
+            mCaptionWidthId = Resources.ID_NULL;
+            mShadowRadiusId = Resources.ID_NULL;
+
+            mOutsetTopId = Resources.ID_NULL;
+            mOutsetBottomId = Resources.ID_NULL;
+            mOutsetLeftId = Resources.ID_NULL;
+            mOutsetRightId = Resources.ID_NULL;
+        }
+    }
+
     static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
         int mWidth;
         int mHeight;
-        float mDensity;
         T mRootView;
 
         void reset() {
             mWidth = 0;
             mHeight = 0;
-            mDensity = 0;
             mRootView = null;
         }
     }
 
-    private static class CaptionWindowManager extends WindowlessWindowManager {
-        CaptionWindowManager(Configuration config, SurfaceControl rootSurface) {
-            super(config, rootSurface, null /* hostInputToken */);
-        }
-
-        @Override
-        public void setConfiguration(Configuration configuration) {
-            super.setConfiguration(configuration);
-        }
-    }
-
     interface SurfaceControlViewHostFactory {
         default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
             return new SurfaceControlViewHost(c, d, wmm);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
new file mode 100644
index 0000000..8949a75
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+-->
+<resources>
+    <!-- Resources used in WindowDecorationTests -->
+    <dimen name="test_freeform_decor_caption_height">32dp</dimen>
+    <dimen name="test_freeform_decor_caption_width">216dp</dimen>
+    <dimen name="test_window_decor_left_outset">10dp</dimen>
+    <dimen name="test_window_decor_top_outset">20dp</dimen>
+    <dimen name="test_window_decor_right_outset">30dp</dimen>
+    <dimen name="test_window_decor_bottom_outset">40dp</dimen>
+    <dimen name="test_window_decor_shadow_radius">5dp</dimen>
+    <dimen name="test_window_decor_resize_handle">10dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 077e9ca..2e328b0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -127,6 +127,7 @@
                 mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
                 mActivityTaskManager, mContext,
                 mContentResolver);
+        mController.setEnableUAnimation(true);
         mShellInit.init();
         mEventTime = 0;
         mShellExecutor.flushAll();
@@ -245,10 +246,10 @@
         // Check that back start and progress is dispatched when first move.
         doMotionEvent(MotionEvent.ACTION_MOVE, 100);
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
-        verify(mIOnBackInvokedCallback).onBackStarted();
         ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
-        verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture());
+        verify(mIOnBackInvokedCallback).onBackStarted(backEventCaptor.capture());
         assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget());
+        verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(any(BackEvent.class));
 
         // Check that back invocation is dispatched.
         mController.setTriggerBack(true);   // Fake trigger back
@@ -276,11 +277,11 @@
 
         triggerBackGesture();
 
-        verify(appCallback, never()).onBackStarted();
+        verify(appCallback, never()).onBackStarted(any(BackEvent.class));
         verify(appCallback, never()).onBackProgressed(backEventCaptor.capture());
         verify(appCallback, times(1)).onBackInvoked();
 
-        verify(mIOnBackInvokedCallback, never()).onBackStarted();
+        verify(mIOnBackInvokedCallback, never()).onBackStarted(any(BackEvent.class));
         verify(mIOnBackInvokedCallback, never()).onBackProgressed(backEventCaptor.capture());
         verify(mIOnBackInvokedCallback, never()).onBackInvoked();
     }
@@ -313,7 +314,7 @@
         doMotionEvent(MotionEvent.ACTION_DOWN, 0);
         doMotionEvent(MotionEvent.ACTION_MOVE, 100);
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
-        verify(mIOnBackInvokedCallback).onBackStarted();
+        verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
     }
 
     @Test
@@ -332,7 +333,7 @@
         doMotionEvent(MotionEvent.ACTION_DOWN, 0);
         doMotionEvent(MotionEvent.ACTION_MOVE, 100);
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
-        verify(mIOnBackInvokedCallback).onBackStarted();
+        verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
     }
 
 
@@ -348,7 +349,7 @@
         // Check that back start and progress is dispatched when first move.
         doMotionEvent(MotionEvent.ACTION_MOVE, 100);
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
-        verify(mIOnBackInvokedCallback).onBackStarted();
+        verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
 
         // Check that back invocation is dispatched.
         mController.setTriggerBack(true);   // Fake trigger back
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
new file mode 100644
index 0000000..3aefc3f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.wm.shell.back;
+
+import static org.junit.Assert.assertEquals;
+
+import android.window.BackEvent;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class TouchTrackerTest {
+    private static final float FAKE_THRESHOLD = 400;
+    private static final float INITIAL_X_LEFT_EDGE = 5;
+    private static final float INITIAL_X_RIGHT_EDGE = FAKE_THRESHOLD - INITIAL_X_LEFT_EDGE;
+    private TouchTracker mTouchTracker;
+
+    @Before
+    public void setUp() throws Exception {
+        mTouchTracker = new TouchTracker();
+        mTouchTracker.setProgressThreshold(FAKE_THRESHOLD);
+    }
+
+    @Test
+    public void generatesProgress_onStart() {
+        mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
+        BackEvent event = mTouchTracker.createStartEvent(null);
+        assertEquals(event.getProgress(), 0f, 0f);
+    }
+
+    @Test
+    public void generatesProgress_leftEdge() {
+        mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
+        float touchX = 10;
+
+        // Pre-commit
+        mTouchTracker.update(touchX, 0);
+        assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
+
+        // Post-commit
+        touchX += 100;
+        mTouchTracker.setTriggerBack(true);
+        mTouchTracker.update(touchX, 0);
+        assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
+
+        // Cancel
+        touchX -= 10;
+        mTouchTracker.setTriggerBack(false);
+        mTouchTracker.update(touchX, 0);
+        assertEquals(getProgress(), 0, 0f);
+
+        // Cancel more
+        touchX -= 10;
+        mTouchTracker.update(touchX, 0);
+        assertEquals(getProgress(), 0, 0f);
+
+        // Restart
+        touchX += 10;
+        mTouchTracker.update(touchX, 0);
+        assertEquals(getProgress(), 0, 0f);
+
+        // Restarted, but pre-commit
+        float restartX = touchX;
+        touchX += 10;
+        mTouchTracker.update(touchX, 0);
+        assertEquals(getProgress(), (touchX - restartX) / FAKE_THRESHOLD, 0f);
+
+        // Restarted, post-commit
+        touchX += 10;
+        mTouchTracker.setTriggerBack(true);
+        mTouchTracker.update(touchX, 0);
+        assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
+    }
+
+    @Test
+    public void generatesProgress_rightEdge() {
+        mTouchTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0, BackEvent.EDGE_RIGHT);
+        float touchX = INITIAL_X_RIGHT_EDGE - 10; // Fake right edge
+
+        // Pre-commit
+        mTouchTracker.update(touchX, 0);
+        assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
+
+        // Post-commit
+        touchX -= 100;
+        mTouchTracker.setTriggerBack(true);
+        mTouchTracker.update(touchX, 0);
+        assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
+
+        // Cancel
+        touchX += 10;
+        mTouchTracker.setTriggerBack(false);
+        mTouchTracker.update(touchX, 0);
+        assertEquals(getProgress(), 0, 0f);
+
+        // Cancel more
+        touchX += 10;
+        mTouchTracker.update(touchX, 0);
+        assertEquals(getProgress(), 0, 0f);
+
+        // Restart
+        touchX -= 10;
+        mTouchTracker.update(touchX, 0);
+        assertEquals(getProgress(), 0, 0f);
+
+        // Restarted, but pre-commit
+        float restartX = touchX;
+        touchX -= 10;
+        mTouchTracker.update(touchX, 0);
+        assertEquals(getProgress(), (restartX - touchX) / FAKE_THRESHOLD, 0f);
+
+        // Restarted, post-commit
+        touchX -= 10;
+        mTouchTracker.setTriggerBack(true);
+        mTouchTracker.update(touchX, 0);
+        assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
+    }
+
+    private float getProgress() {
+        return mTouchTracker.createProgressEvent().getProgress();
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index c850a3b..79b520c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -20,6 +20,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
@@ -35,10 +37,12 @@
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.testing.AndroidTestingRunner;
 import android.window.DisplayAreaInfo;
+import android.window.TransitionRequestInfo;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 import android.window.WindowContainerTransaction.Change;
@@ -243,6 +247,44 @@
         assertThat(op2.getContainer()).isEqualTo(token2.binder());
     }
 
+    @Test
+    public void testHandleTransitionRequest_desktopModeNotActive_returnsNull() {
+        when(DesktopModeStatus.isActive(any())).thenReturn(false);
+        WindowContainerTransaction wct = mController.handleRequest(
+                new Binder(),
+                new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+        assertThat(wct).isNull();
+    }
+
+    @Test
+    public void testHandleTransitionRequest_notTransitOpen_returnsNull() {
+        WindowContainerTransaction wct = mController.handleRequest(
+                new Binder(),
+                new TransitionRequestInfo(TRANSIT_TO_FRONT, null /* trigger */, null /* remote */));
+        assertThat(wct).isNull();
+    }
+
+    @Test
+    public void testHandleTransitionRequest_notFreeform_returnsNull() {
+        ActivityManager.RunningTaskInfo trigger = new ActivityManager.RunningTaskInfo();
+        trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        WindowContainerTransaction wct = mController.handleRequest(
+                new Binder(),
+                new TransitionRequestInfo(TRANSIT_TO_FRONT, trigger, null /* remote */));
+        assertThat(wct).isNull();
+    }
+
+    @Test
+    public void testHandleTransitionRequest_returnsWct() {
+        ActivityManager.RunningTaskInfo trigger = new ActivityManager.RunningTaskInfo();
+        trigger.token = new MockToken().mToken;
+        trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        WindowContainerTransaction wct = mController.handleRequest(
+                mock(IBinder.class),
+                new TransitionRequestInfo(TRANSIT_OPEN, trigger, null /* remote */));
+        assertThat(wct).isNotNull();
+    }
+
     private static class MockToken {
         private final WindowContainerToken mToken;
         private final IBinder mBinder;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 9b28d11..aaa5c8a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -19,6 +19,7 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -38,7 +39,7 @@
     @Test
     fun addActiveTask_listenerNotifiedAndTaskIsActive() {
         val listener = TestListener()
-        repo.addListener(listener)
+        repo.addActiveTaskListener(listener)
 
         repo.addActiveTask(1)
         assertThat(listener.activeTaskChangedCalls).isEqualTo(1)
@@ -48,7 +49,7 @@
     @Test
     fun addActiveTask_sameTaskDoesNotNotify() {
         val listener = TestListener()
-        repo.addListener(listener)
+        repo.addActiveTaskListener(listener)
 
         repo.addActiveTask(1)
         repo.addActiveTask(1)
@@ -58,7 +59,7 @@
     @Test
     fun addActiveTask_multipleTasksAddedNotifiesForEach() {
         val listener = TestListener()
-        repo.addListener(listener)
+        repo.addActiveTaskListener(listener)
 
         repo.addActiveTask(1)
         repo.addActiveTask(2)
@@ -68,7 +69,7 @@
     @Test
     fun removeActiveTask_listenerNotifiedAndTaskNotActive() {
         val listener = TestListener()
-        repo.addListener(listener)
+        repo.addActiveTaskListener(listener)
 
         repo.addActiveTask(1)
         repo.removeActiveTask(1)
@@ -80,7 +81,7 @@
     @Test
     fun removeActiveTask_removeNotExistingTaskDoesNotNotify() {
         val listener = TestListener()
-        repo.addListener(listener)
+        repo.addActiveTaskListener(listener)
         repo.removeActiveTask(99)
         assertThat(listener.activeTaskChangedCalls).isEqualTo(0)
     }
@@ -90,10 +91,69 @@
         assertThat(repo.isActiveTask(99)).isFalse()
     }
 
-    class TestListener : DesktopModeTaskRepository.Listener {
+    @Test
+    fun addListener_notifiesVisibleFreeformTask() {
+        repo.updateVisibleFreeformTasks(1, true)
+        val listener = TestVisibilityListener()
+        val executor = TestShellExecutor()
+        repo.addVisibleTasksListener(listener, executor)
+        executor.flushAll()
+
+        assertThat(listener.hasVisibleFreeformTasks).isTrue()
+        assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(1)
+    }
+
+    @Test
+    fun updateVisibleFreeformTasks_addVisibleTasksNotifiesListener() {
+        val listener = TestVisibilityListener()
+        val executor = TestShellExecutor()
+        repo.addVisibleTasksListener(listener, executor)
+        repo.updateVisibleFreeformTasks(1, true)
+        repo.updateVisibleFreeformTasks(2, true)
+        executor.flushAll()
+
+        assertThat(listener.hasVisibleFreeformTasks).isTrue()
+        // Equal to 2 because adding the listener notifies the current state
+        assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(2)
+    }
+
+    @Test
+    fun updateVisibleFreeformTasks_removeVisibleTasksNotifiesListener() {
+        val listener = TestVisibilityListener()
+        val executor = TestShellExecutor()
+        repo.addVisibleTasksListener(listener, executor)
+        repo.updateVisibleFreeformTasks(1, true)
+        repo.updateVisibleFreeformTasks(2, true)
+        executor.flushAll()
+
+        assertThat(listener.hasVisibleFreeformTasks).isTrue()
+        repo.updateVisibleFreeformTasks(1, false)
+        executor.flushAll()
+
+        // Equal to 2 because adding the listener notifies the current state
+        assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(2)
+
+        repo.updateVisibleFreeformTasks(2, false)
+        executor.flushAll()
+
+        assertThat(listener.hasVisibleFreeformTasks).isFalse()
+        assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(3)
+    }
+
+    class TestListener : DesktopModeTaskRepository.ActiveTasksListener {
         var activeTaskChangedCalls = 0
         override fun onActiveTasksChanged() {
             activeTaskChangedCalls++
         }
     }
+
+    class TestVisibilityListener : DesktopModeTaskRepository.VisibleTasksListener {
+        var hasVisibleFreeformTasks = false
+        var visibleFreeformTaskChangedCalls = 0
+
+        override fun onVisibilityChanged(hasVisibleTasks: Boolean) {
+            hasVisibleFreeformTasks = hasVisibleTasks
+            visibleFreeformTaskChangedCalls++
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index fa62b9c..4d37e5d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -50,11 +50,13 @@
 import android.window.WindowContainerTransaction;
 
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.tests.R;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -76,13 +78,9 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class WindowDecorationTests extends ShellTestCase {
-    private static final int CAPTION_HEIGHT_DP = 32;
-    private static final int CAPTION_WIDTH_DP = 216;
-    private static final int SHADOW_RADIUS_DP = 5;
     private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400);
     private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60);
 
-    private final Rect mOutsetsDp = new Rect();
     private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =
             new WindowDecoration.RelayoutResult<>();
 
@@ -104,6 +102,7 @@
     private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>();
     private SurfaceControl.Transaction mMockSurfaceControlStartT;
     private SurfaceControl.Transaction mMockSurfaceControlFinishT;
+    private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams();
 
     @Before
     public void setUp() {
@@ -147,7 +146,11 @@
         // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
         // 64px.
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        mOutsetsDp.set(10, 20, 30, 40);
+        mRelayoutParams.setOutsets(
+                R.dimen.test_window_decor_left_outset,
+                R.dimen.test_window_decor_top_outset,
+                R.dimen.test_window_decor_right_outset,
+                R.dimen.test_window_decor_bottom_outset);
 
         final SurfaceControl taskSurface = mock(SurfaceControl.class);
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -197,8 +200,11 @@
         // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
         // 64px.
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        mOutsetsDp.set(10, 20, 30, 40);
-
+        mRelayoutParams.setOutsets(
+                R.dimen.test_window_decor_left_outset,
+                R.dimen.test_window_decor_top_outset,
+                R.dimen.test_window_decor_right_outset,
+                R.dimen.test_window_decor_bottom_outset);
         final SurfaceControl taskSurface = mock(SurfaceControl.class);
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
 
@@ -226,16 +232,17 @@
         verify(mMockSurfaceControlStartT).show(captionContainerSurface);
 
         verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
+
         verify(mMockSurfaceControlViewHost)
                 .setView(same(mMockView),
                         argThat(lp -> lp.height == 64
-                                && lp.width == 300
+                                && lp.width == 432
                                 && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0));
         if (ViewRootImpl.CAPTION_ON_SHELL) {
             verify(mMockView).setTaskFocusState(true);
             verify(mMockWindowContainerTransaction)
                     .addRectInsetsProvider(taskInfo.token,
-                            new Rect(100, 300, 400, 364),
+                            new Rect(100, 300, 400, 332),
                             new int[] { InsetsState.ITYPE_CAPTION_BAR });
         }
 
@@ -248,7 +255,6 @@
 
         assertEquals(380, mRelayoutResult.mWidth);
         assertEquals(220, mRelayoutResult.mHeight);
-        assertEquals(2, mRelayoutResult.mDensity, 0.f);
     }
 
     @Test
@@ -287,7 +293,11 @@
         // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
         // 64px.
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        mOutsetsDp.set(10, 20, 30, 40);
+        mRelayoutParams.setOutsets(
+                R.dimen.test_window_decor_left_outset,
+                R.dimen.test_window_decor_top_outset,
+                R.dimen.test_window_decor_right_outset,
+                R.dimen.test_window_decor_bottom_outset);
 
         final SurfaceControl taskSurface = mock(SurfaceControl.class);
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -358,7 +368,8 @@
 
     private TestWindowDecoration createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
-        return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
+        return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(),
+                mMockDisplayController, mMockShellTaskOrganizer,
                 taskInfo, testSurface,
                 new MockObjectSupplier<>(mMockSurfaceControlBuilders,
                         () -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))),
@@ -410,9 +421,13 @@
 
         @Override
         void relayout(ActivityManager.RunningTaskInfo taskInfo) {
-            relayout(null /* taskInfo */, 0 /* layoutResId */, mMockView, CAPTION_HEIGHT_DP,
-                    CAPTION_WIDTH_DP, mOutsetsDp, SHADOW_RADIUS_DP, mMockSurfaceControlStartT,
-                    mMockSurfaceControlFinishT, mMockWindowContainerTransaction, mRelayoutResult);
+            mRelayoutParams.mLayoutResId = 0;
+            mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height;
+            mRelayoutParams.mCaptionWidthId = R.dimen.test_freeform_decor_caption_width;
+            mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius;
+
+            relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
+                    mMockWindowContainerTransaction, mMockView, mRelayoutResult);
         }
     }
 }
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
index 2fe7b16..262f5f1 100644
--- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
@@ -100,16 +100,12 @@
         @Override
         public void onConnectionStateChange(BluetoothGatt gatt, int status,
                 int newState) {
+            Log.d(TAG, "onConnectionStateChange() status: " + status + ", newState: " + newState);
             String intentAction;
             if (newState == BluetoothProfile.STATE_CONNECTED) {
                 Log.d(TAG, "Connected to GATT server.");
                 Log.d(TAG, "Attempting to start service discovery:" +
                         mBluetoothGatt.discoverServices());
-                if (!mBluetoothGatt.requestMtu(MAX_PACKET_SIZE)) {
-                    Log.e(TAG, "request mtu failed");
-                    mPacketEncoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
-                    mPacketDecoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
-                }
             } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                 Log.i(TAG, "Disconnected from GATT server.");
                 close();
@@ -118,6 +114,7 @@
 
         @Override
         public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+            Log.d(TAG, "onServicesDiscovered() status: " +  status);
             if (status == BluetoothGatt.GATT_SUCCESS) {
                 BluetoothGattService service = gatt.getService(MIDI_SERVICE);
                 if (service != null) {
@@ -137,9 +134,14 @@
                         // Specification says to read the characteristic first and then
                         // switch to receiving notifications
                         mBluetoothGatt.readCharacteristic(characteristic);
-                    }
 
-                    openBluetoothDevice(mBluetoothDevice);
+                        // Request higher MTU size
+                        if (!gatt.requestMtu(MAX_PACKET_SIZE)) {
+                            Log.e(TAG, "request mtu failed");
+                            mPacketEncoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
+                            mPacketDecoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
+                        }
+                    }
                 }
             } else {
                 Log.e(TAG, "onServicesDiscovered received: " + status);
@@ -235,13 +237,13 @@
             System.arraycopy(buffer, 0, mCachedBuffer, 0, count);
 
             if (DEBUG) {
-                logByteArray("Sent ", mCharacteristic.getValue(), 0,
-                       mCharacteristic.getValue().length);
+                logByteArray("Sent ", mCachedBuffer, 0, mCachedBuffer.length);
             }
 
-            if (mBluetoothGatt.writeCharacteristic(mCharacteristic, mCachedBuffer,
-                    mCharacteristic.getWriteType()) != BluetoothGatt.GATT_SUCCESS) {
-                Log.w(TAG, "could not write characteristic to Bluetooth GATT");
+            int result = mBluetoothGatt.writeCharacteristic(mCharacteristic, mCachedBuffer,
+                    mCharacteristic.getWriteType());
+            if (result != BluetoothGatt.GATT_SUCCESS) {
+                Log.w(TAG, "could not write characteristic to Bluetooth GATT. result: " + result);
                 return false;
             }
 
@@ -254,6 +256,10 @@
         mBluetoothDevice = device;
         mService = service;
 
+        // Set a small default packet size in case there is an issue with configuring MTUs.
+        mPacketEncoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
+        mPacketDecoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
+
         mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, mGattCallback);
 
         mContext = context;
diff --git a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
index 18b8ca9..77dfbee 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_label" msgid="4470785958457506021">"隨附裝置管理員"</string>
+    <string name="app_label" msgid="4470785958457506021">"隨附裝置管理工具"</string>
     <string name="confirmation_title" msgid="3785000297483688997">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;存取「<xliff:g id="DEVICE_NAME">%2$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"手錶"</string>
     <string name="chooser_title" msgid="2262294130493605839">"選擇要讓「<xliff:g id="APP_NAME">%2$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;管理的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index 5c9ab7b..4e7e367 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -350,7 +350,7 @@
                 }
                 return;
             }
-            if (DEBUG) Log.i(TAG, "onDeviceFound() " + device.toShortString() + " - New device.");
+            Log.i(TAG, "onDeviceFound() " + device.toShortString() + " - New device.");
 
             // First: make change.
             mDevicesFound.add(device);
@@ -363,9 +363,9 @@
         });
     }
 
-    private void onDeviceLost(@Nullable DeviceFilterPair<?> device) {
+    private void onDeviceLost(@NonNull DeviceFilterPair<?> device) {
         runOnMainThread(() -> {
-            if (DEBUG) Log.i(TAG, "onDeviceLost(), device=" + device.toShortString());
+            Log.i(TAG, "onDeviceLost(), device=" + device.toShortString());
 
             // First: make change.
             mDevicesFound.remove(device);
diff --git a/packages/PackageInstaller/res/values-as/strings.xml b/packages/PackageInstaller/res/values-as/strings.xml
index dd776a9..8405335 100644
--- a/packages/PackageInstaller/res/values-as/strings.xml
+++ b/packages/PackageInstaller/res/values-as/strings.xml
@@ -28,11 +28,11 @@
     <string name="install_confirm_question_update" msgid="3348888852318388584">"আপুনি এই এপ্‌টো আপডে’ট কৰিবলৈ বিচাৰেনে?"</string>
     <string name="install_failed" msgid="5777824004474125469">"এপ্ ইনষ্টল কৰা হোৱা নাই।"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"পেকেজটোৰ ইনষ্টল অৱৰোধ কৰা হৈছে।"</string>
-    <string name="install_failed_conflict" msgid="3493184212162521426">"এপটো ইনষ্টল কৰিব পৰা নগ\'ল কাৰণ ইয়াৰ সৈতে আগৰে পৰা থকা এটা পেকেজৰ সংঘাত হৈছে।"</string>
-    <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"আপোনাৰ টেবলেটৰ সৈতে খাপ নোখোৱাৰ বাবে এপটো ইনষ্টল কৰা নহ\'ল।"</string>
-    <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"আপোনাৰ টিভিত এই এপটো নচলে"</string>
-    <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"আপোনাৰ ফ\'নৰ সৈতে খাপ নোখোৱাৰ বাবে এপটো ইনষ্টল কৰা নহ\'ল।"</string>
-    <string name="install_failed_invalid_apk" msgid="8581007676422623930">"পেকেজটো মান্য নোহোৱাৰ বাবে এপটো ইনষ্টল কৰা নহ\'ল।"</string>
+    <string name="install_failed_conflict" msgid="3493184212162521426">"এপ্‌টো ইনষ্টল কৰিব পৰা নগ\'ল কাৰণ ইয়াৰ সৈতে আগৰে পৰা থকা এটা পেকেজৰ সংঘাত হৈছে।"</string>
+    <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"আপোনাৰ টেবলেটৰ সৈতে খাপ নোখোৱাৰ বাবে এপ্‌টো ইনষ্টল কৰা নহ\'ল।"</string>
+    <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"আপোনাৰ টিভিত এই এপ্‌টো নচলে"</string>
+    <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"আপোনাৰ ফ\'নৰ সৈতে খাপ নোখোৱাৰ বাবে এপ্‌টো ইনষ্টল কৰা নহ\'ল।"</string>
+    <string name="install_failed_invalid_apk" msgid="8581007676422623930">"পেকেজটো মান্য নোহোৱাৰ বাবে এপ্‌টো ইনষ্টল কৰা নহ\'ল।"</string>
     <string name="install_failed_msg" product="tablet" msgid="6298387264270562442">"আপোনাৰ টে\'বলেটত <xliff:g id="APP_NAME">%1$s</xliff:g> ইনষ্টল কৰিব পৰা নগ\'ল৷"</string>
     <string name="install_failed_msg" product="tv" msgid="1920009940048975221">"আপোনাৰ টিভিত <xliff:g id="APP_NAME">%1$s</xliff:g> ইনষ্টল কৰিব পৰা নগ\'ল।"</string>
     <string name="install_failed_msg" product="default" msgid="6484461562647915707">"আপোনাৰ ফ\'নত <xliff:g id="APP_NAME">%1$s</xliff:g> ইনষ্টল কৰিব পৰা নগ\'ল৷"</string>
@@ -44,21 +44,21 @@
     <string name="manage_applications" msgid="5400164782453975580">"এপ্ পৰিচালনা"</string>
     <string name="out_of_space_dlg_title" msgid="4156690013884649502">"খালী ঠাই নাই"</string>
     <string name="out_of_space_dlg_text" msgid="8727714096031856231">"<xliff:g id="APP_NAME">%1$s</xliff:g> ইনষ্টল কৰিব পৰা নগ\'ল। কিছু খালী ঠাই উলিয়াই আকৌ চেষ্টা কৰক৷"</string>
-    <string name="app_not_found_dlg_title" msgid="5107924008597470285">"এপটো পোৱা নগ\'ল"</string>
-    <string name="app_not_found_dlg_text" msgid="5219983779377811611">"ইনষ্টল কৰি ৰখা এপৰ তালিকাত এই এপটো পোৱা নগ\'ল।"</string>
+    <string name="app_not_found_dlg_title" msgid="5107924008597470285">"এপ্‌টো পোৱা নগ\'ল"</string>
+    <string name="app_not_found_dlg_text" msgid="5219983779377811611">"ইনষ্টল কৰি ৰখা এপৰ তালিকাত এই এপ্‌টো পোৱা নগ\'ল।"</string>
     <string name="user_is_not_allowed_dlg_title" msgid="6915293433252210232">"অনুমতি নাই"</string>
     <string name="user_is_not_allowed_dlg_text" msgid="3468447791330611681">"বর্তমানৰ ব্যৱহাৰকাৰীজনক এইটো আনইনষ্টল কৰিবলৈ অনুমতি দিয়া হোৱা নাই।"</string>
     <string name="generic_error_dlg_title" msgid="5863195085927067752">"আসোঁৱাহ"</string>
     <string name="generic_error_dlg_text" msgid="5287861443265795232">"এপ্ আনইনষ্টল কৰিব পৰা নগ\'ল।"</string>
     <string name="uninstall_application_title" msgid="4045420072401428123">"এপ্ আনইনষ্টল কৰক"</string>
     <string name="uninstall_update_title" msgid="824411791011583031">"আপডে’ট আনইনষ্টল কৰক"</string>
-    <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> হৈছে তলৰ এপটোৰ এটা অংশ:"</string>
+    <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> হৈছে তলৰ এপ্‌টোৰ এটা অংশ:"</string>
     <string name="uninstall_application_text" msgid="3816830743706143980">"আপুনি এই এপ্‌টো আনইনষ্টল কৰিব বিচাৰে নেকি?"</string>
-    <string name="uninstall_application_text_all_users" msgid="575491774380227119">"আপুনি "<b>"সকলো"</b>" ব্যৱহাৰকাৰীৰ বাবে এই এপটো আনইনষ্টল কৰিব বিচাৰেনে? এপ্লিকেশ্বন আৰু ইয়াৰ ডেটা ডিভাইচটোত থকা "<b>"সকলো"</b>" ব্যৱহাৰকাৰীৰ পৰা আঁতৰোৱা হ\'ব৷"</string>
-    <string name="uninstall_application_text_user" msgid="498072714173920526">"আপুনি ব্যৱহাৰকাৰীৰ <xliff:g id="USERNAME">%1$s</xliff:g> বাবে এই এপটো আনইনষ্টল কৰিব বিচাৰেনে?"</string>
+    <string name="uninstall_application_text_all_users" msgid="575491774380227119">"আপুনি "<b>"সকলো"</b>" ব্যৱহাৰকাৰীৰ বাবে এই এপ্‌টো আনইনষ্টল কৰিব বিচাৰেনে? এপ্লিকেশ্বন আৰু ইয়াৰ ডেটা ডিভাইচটোত থকা "<b>"সকলো"</b>" ব্যৱহাৰকাৰীৰ পৰা আঁতৰোৱা হ\'ব৷"</string>
+    <string name="uninstall_application_text_user" msgid="498072714173920526">"আপুনি ব্যৱহাৰকাৰীৰ <xliff:g id="USERNAME">%1$s</xliff:g> বাবে এই এপ্‌টো আনইনষ্টল কৰিব বিচাৰেনে?"</string>
     <string name="uninstall_application_text_current_user_work_profile" msgid="8788387739022366193">"আপুনি নিজৰ কৰ্মস্থানৰ প্ৰ’ফাইলৰ পৰা এই এপ্‌টো আনইনষ্টল কৰিব বিচাৰেনে?"</string>
     <string name="uninstall_update_text" msgid="863648314632448705">"এই এপ্‌টোৰ ফেক্টৰী সংস্কৰণ ব্যৱহাৰ কৰিব বিচাৰেনে? আটাইবোৰ ডেটা মচা হ\'ব।"</string>
-    <string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"এই এপটোৰ ফেক্টৰী সংস্কৰণ ব্যৱহাৰ কৰিব বিচাৰেনে? সকলো ডেটা মচা হ\'ব। কর্মস্থানৰ প্ৰফাইল থকা ব্যৱহাৰকাৰীৰ লগতে ডিভাইচটোৰ সকলো ব্যৱহাৰকাৰীৰ ওপৰত ইয়াৰ প্ৰভাৱ পৰিব।"</string>
+    <string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"এই এপ্‌টোৰ ফেক্টৰী সংস্কৰণ ব্যৱহাৰ কৰিব বিচাৰেনে? সকলো ডেটা মচা হ\'ব। কর্মস্থানৰ প্ৰফাইল থকা ব্যৱহাৰকাৰীৰ লগতে ডিভাইচটোৰ সকলো ব্যৱহাৰকাৰীৰ ওপৰত ইয়াৰ প্ৰভাৱ পৰিব।"</string>
     <string name="uninstall_keep_data" msgid="7002379587465487550">"এপৰ ডেটাৰ <xliff:g id="SIZE">%1$s</xliff:g> ৰাখক"</string>
     <string name="uninstalling_notification_channel" msgid="840153394325714653">"আনইনষ্টল কৰি থকা হৈছে"</string>
     <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"যিবোৰ আনইনষ্টল পৰা নগ\'ল"</string>
@@ -70,9 +70,9 @@
     <string name="uninstall_failed_app" msgid="5506028705017601412">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> আনইনষ্টল কৰিব পৰা নগ\'ল।"</string>
     <string name="uninstall_failed_device_policy_manager" msgid="785293813665540305">"ডিভাইচৰ সক্ৰিয় প্ৰশাসক এপ্ আনইনষ্টল কৰিব নোৱাৰি"</string>
     <string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"<xliff:g id="USERNAME">%1$s</xliff:g>ৰ সক্ৰিয় ডিভাইচৰ প্ৰশাসকীয় এপ্ আনইনষ্টল কৰিব নোৱাৰি"</string>
-    <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"এই এপটো কিছুসংখ্যক ব্যৱহাৰকাৰী বা প্ৰ\'ফাইলৰ বাবে প্ৰয়োজনীয় আৰু বাকীসকলৰ বাবে ইয়াক আনইনষ্টল কৰা হৈছে"</string>
-    <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"আপোনাৰ প্ৰ\'ফাইলৰ বাবে এই এপটোৰ প্ৰয়োজন আছে গতিকে আনইনষ্টল কৰিব পৰা নাযায়।"</string>
-    <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"এই এপটো আনইনষ্টল কৰিব পৰা নাযায় কাৰণ আপোনাৰ ডিভাইচৰ প্ৰশাসকে এই এপ্ ৰখাটো বাধ্যতামূলক কৰি ৰাখিছে।"</string>
+    <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"এই এপ্‌টো কিছুসংখ্যক ব্যৱহাৰকাৰী বা প্ৰ\'ফাইলৰ বাবে প্ৰয়োজনীয় আৰু বাকীসকলৰ বাবে ইয়াক আনইনষ্টল কৰা হৈছে"</string>
+    <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"আপোনাৰ প্ৰ\'ফাইলৰ বাবে এই এপ্‌টোৰ প্ৰয়োজন আছে গতিকে আনইনষ্টল কৰিব পৰা নাযায়।"</string>
+    <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"এই এপ্‌টো আনইনষ্টল কৰিব পৰা নাযায় কাৰণ আপোনাৰ ডিভাইচৰ প্ৰশাসকে এই এপ্ ৰখাটো বাধ্যতামূলক কৰি ৰাখিছে।"</string>
     <string name="manage_device_administrators" msgid="3092696419363842816">"ডিভাইচৰ প্ৰশাসক এপসমূহ পৰিচালনা কৰক"</string>
     <string name="manage_users" msgid="1243995386982560813">"ব্যৱহাৰকাৰী পৰিচালনা কৰক"</string>
     <string name="uninstall_failed_msg" msgid="2176744834786696012">"<xliff:g id="APP_NAME">%1$s</xliff:g> আনইনষ্টল কৰিব নোৱাৰি।"</string>
@@ -84,9 +84,9 @@
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"আপোনাৰ সুৰক্ষাৰ বাবে আপোনাৰ টেবলেটটোক বৰ্তমান এই উৎসটোৰ পৰা অজ্ঞাত এপ্‌ ইনষ্টল কৰাৰ অনুমতি দিয়া হোৱা নাই। আপুনি এইটো ছেটিঙত সলনি কৰিব পাৰে।"</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"আপোনাৰ সুৰক্ষাৰ বাবে আপোনাৰ টিভিটোক বৰ্তমান এই উৎসটোৰ পৰা অজ্ঞাত এপ্‌ ইনষ্টল কৰাৰ অনুমতি দিয়া হোৱা নাই। আপুনি এইটো ছেটিঙত সলনি কৰিব পাৰে।"</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"আপোনাৰ সুৰক্ষাৰ বাবে আপোনাৰ ফ’নটোক বৰ্তমান এই উৎসটোৰ পৰা অজ্ঞাত এপ্‌ ইনষ্টল কৰাৰ অনুমতি দিয়া হোৱা নাই। আপুনি এইটো ছেটিঙত সলনি কৰিব পাৰে।"</string>
-    <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"আপোনাৰ ফ\'ন আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপটো ইনষ্টল কৰি এপটোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string>
-    <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"আপোনাৰ টেবলেট আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপটো ইনষ্টল কৰি এপটোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string>
-    <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"আপোনাৰ টিভি আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপটো ইনষ্টল কৰি এপটোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string>
+    <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"আপোনাৰ ফ\'ন আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপ্‌টো ইনষ্টল কৰি এপ্‌টোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string>
+    <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"আপোনাৰ টেবলেট আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপ্‌টো ইনষ্টল কৰি এপ্‌টোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string>
+    <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"আপোনাৰ টিভি আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপ্‌টো ইনষ্টল কৰি এপ্‌টোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string>
     <string name="anonymous_source_continue" msgid="4375745439457209366">"অব্যাহত ৰাখক"</string>
     <string name="external_sources_settings" msgid="4046964413071713807">"ছেটিং"</string>
     <string name="wear_app_channel" msgid="1960809674709107850">"ৱেৰ এপসমূহ ইনষ্টল/আনইনষ্টল কৰি থকা হৈছে"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 734eb33..6bb7595 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -209,7 +209,7 @@
     <string name="tts_engine_settings_button" msgid="477155276199968948">"የፍርግም ቅንብሮችን ያስጀምሩ"</string>
     <string name="tts_engine_preference_section_title" msgid="3861562305498624904">"የተመረጠ ፍርግም"</string>
     <string name="tts_general_section_title" msgid="8919671529502364567">"አጠቃላይ"</string>
-    <string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"የንግግር ድምጽ ውፍረት ዳግም አስጀምር"</string>
+    <string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"የንግግር ድምፅ ውፍረት ዳግም አስጀምር"</string>
     <string name="tts_reset_speech_pitch_summary" msgid="6822904157021406449">"ጽሑፉ የሚነገርበትን የድምጽ ውፍረት ወደ ነባሪ ዳግም አስጀምር።"</string>
   <string-array name="tts_rate_entries">
     <item msgid="9004239613505400644">"በጣም ቀርፋፋ"</item>
@@ -407,7 +407,7 @@
     <string name="force_resizable_activities" msgid="7143612144399959606">"እንቅስቃሴዎች ዳግመኛ እንዲመጣጠኑ አስገድድ"</string>
     <string name="force_resizable_activities_summary" msgid="2490382056981583062">"የዝርዝር ሰነድ እሴቶች ምንም ይሁኑ ምን ለበርካታ መስኮቶች ሁሉንም እንቅስቃሴዎች መጠናቸው የሚቀየሩ እንዲሆኑ ያደርጋቸዋል።"</string>
     <string name="enable_freeform_support" msgid="7599125687603914253">"የነጻ ቅርጽ መስኮቶችን ያንቁ"</string>
-    <string name="enable_freeform_support_summary" msgid="1822862728719276331">"የሙከራ ነጻ መልክ መስኮቶች ድጋፍን አንቃ"</string>
+    <string name="enable_freeform_support_summary" msgid="1822862728719276331">"የሙከራ ነፃ መልክ መስኮቶች ድጋፍን አንቃ"</string>
     <string name="desktop_mode" msgid="2389067840550544462">"የዴስክቶፕ ሁነታ"</string>
     <string name="local_backup_password_title" msgid="4631017948933578709">"የዴስክቶፕ መጠባበቂያ ይለፍ ቃል"</string>
     <string name="local_backup_password_summary_none" msgid="7646898032616361714">"ዴስክቶፕ ሙሉ ምትኬዎች በአሁኑ ሰዓት አልተጠበቁም"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index c4e9dd4..976949c 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -268,7 +268,7 @@
     <string name="keep_screen_on" msgid="1187161672348797558">"জাগ্ৰত কৰি ৰাখক"</string>
     <string name="keep_screen_on_summary" msgid="1510731514101925829">"চ্চাৰ্জ হৈ থকাৰ সময়ত স্ক্ৰীন কেতিয়াও সুপ্ত অৱস্থালৈ নাযায়"</string>
     <string name="bt_hci_snoop_log" msgid="7291287955649081448">"ব্লুটুথ HCI স্নুপ ল’গ সক্ষম কৰক"</string>
-    <string name="bt_hci_snoop_log_summary" msgid="6808538971394092284">"ব্লুটুথ পেকেট সংগ্ৰহ কৰক। (এই ছেটিংটো সলনি কৰাৰ পিছত ব্লুটুথ ট’গল কৰক)"</string>
+    <string name="bt_hci_snoop_log_summary" msgid="6808538971394092284">"ব্লুটুথ পেকেট সংগ্ৰহ কৰক। (এই ছেটিংটো সলনি কৰাৰ পাছত ব্লুটুথ ট’গল কৰক)"</string>
     <string name="oem_unlock_enable" msgid="5334869171871566731">"ঔইএম আনলক"</string>
     <string name="oem_unlock_enable_summary" msgid="5857388174390953829">"বুটল\'ডাৰটো আনলক কৰিবলৈ অনুমতি দিয়ক"</string>
     <string name="confirm_enable_oem_unlock_title" msgid="8249318129774367535">"ঔইএম আনলক কৰাৰ অনুমতি দিবনে?"</string>
@@ -515,8 +515,8 @@
     <string name="active_input_method_subtypes" msgid="4232680535471633046">"সক্ৰিয়হৈ থকা ইনপুট পদ্ধতিসমূহ"</string>
     <string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"ছিষ্টেমৰ ভাষা ব্যৱহাৰ কৰক"</string>
     <string name="failed_to_open_app_settings_toast" msgid="764897252657692092">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>ৰ ছেটিং খুলিব পৰা নগ\'ল"</string>
-    <string name="ime_security_warning" msgid="6547562217880551450">"এই ইনপুট পদ্ধতিটোৱে আপুনি টাইপ কৰা আপোনাৰ ব্যক্তিগত ডেটা যেনে পাছৱৰ্ডসমূহ আৰু ক্ৰেডিট কাৰ্ডৰ নম্বৰসমূহকে ধৰি আটাইবোৰ পাঠ সংগ্ৰহ কৰিবলৈ সক্ষম হ\'ব পাৰে। <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> এপটোৰ লগত ই সংলগ্ন। এই ইনপুট পদ্ধতিটো ব্যৱহাৰ কৰেনে?"</string>
-    <string name="direct_boot_unaware_dialog_message" msgid="7845398276735021548">"টোকা: ৰিবুট কৰাৰ পিছত আপুনি ফ\'নটো আনলক নকৰালৈকে এই এপটো ষ্টাৰ্ট নহ’ব"</string>
+    <string name="ime_security_warning" msgid="6547562217880551450">"এই ইনপুট পদ্ধতিটোৱে আপুনি টাইপ কৰা আপোনাৰ ব্যক্তিগত ডেটা যেনে পাছৱৰ্ডসমূহ আৰু ক্ৰেডিট কাৰ্ডৰ নম্বৰসমূহকে ধৰি আটাইবোৰ পাঠ সংগ্ৰহ কৰিবলৈ সক্ষম হ\'ব পাৰে। <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> এপ্‌টোৰ লগত ই সংলগ্ন। এই ইনপুট পদ্ধতিটো ব্যৱহাৰ কৰেনে?"</string>
+    <string name="direct_boot_unaware_dialog_message" msgid="7845398276735021548">"টোকা: ৰিবুট কৰাৰ পাছত আপুনি ফ\'নটো আনলক নকৰালৈকে এই এপ্‌টো ষ্টাৰ্ট নহ’ব"</string>
     <string name="ims_reg_title" msgid="8197592958123671062">"আইএমএছ পঞ্জীয়ন স্থিতি"</string>
     <string name="ims_reg_status_registered" msgid="884916398194885457">"পঞ্জীকৃত"</string>
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"পঞ্জীকৃত নহয়"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 98cc18e..b6eb82e 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -247,7 +247,7 @@
     <string name="adb_paired_devices_title" msgid="5268997341526217362">"Appareils associés"</string>
     <string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Actuellement connecté"</string>
     <string name="adb_wireless_device_details_title" msgid="7129369670526565786">"Infos sur l\'appareil"</string>
-    <string name="adb_device_forget" msgid="193072400783068417">"Supprimer"</string>
+    <string name="adb_device_forget" msgid="193072400783068417">"Retirer"</string>
     <string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"Empreinte de l\'appareil : <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string>
     <string name="adb_wireless_connection_failed_title" msgid="664211177427438438">"Échec de la connexion"</string>
     <string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"Vérifiez que l\'appareil <xliff:g id="DEVICE_NAME">%1$s</xliff:g> est connecté au bon réseau"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 5d5ebe7..c17a1d5 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -509,7 +509,7 @@
     <string name="screen_zoom_summary_extremely_large" msgid="1438045624562358554">"అతి పెద్దగా"</string>
     <string name="screen_zoom_summary_custom" msgid="3468154096832912210">"అనుకూలం (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string>
     <string name="content_description_menu_button" msgid="6254844309171779931">"మెనూ"</string>
-    <string name="retail_demo_reset_message" msgid="5392824901108195463">"డెమో మోడ్‌లో ఫ్యాక్టరీ రీసెట్‌ను నిర్వహించడానికి పాస్‌వర్డ్‌ను నమోదు చేయండి"</string>
+    <string name="retail_demo_reset_message" msgid="5392824901108195463">"డెమో మోడ్‌లో ఫ్యాక్టరీ రీసెట్‌ను మేనేజ్ చేయడానికి పాస్‌వర్డ్‌ను నమోదు చేయండి"</string>
     <string name="retail_demo_reset_next" msgid="3688129033843885362">"తర్వాత"</string>
     <string name="retail_demo_reset_title" msgid="1866911701095959800">"పాస్‌వర్డ్ అవసరం"</string>
     <string name="active_input_method_subtypes" msgid="4232680535471633046">"సక్రియ ఇన్‌పుట్ పద్ధతులు"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 5662ce6..6bc1160 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -356,7 +356,7 @@
      * @return {@code true}, if the device should pair automatically; Otherwise, return
      * {@code false}.
      */
-    public synchronized boolean shouldPairByCsip(BluetoothDevice device, int groupId) {
+    private synchronized boolean shouldPairByCsip(BluetoothDevice device, int groupId) {
         boolean isOngoingSetMemberPair = mOngoingSetMemberPair != null;
         int bondState = device.getBondState();
         if (isOngoingSetMemberPair || bondState != BluetoothDevice.BOND_NONE
@@ -365,13 +365,47 @@
                     + " , device.getBondState: " + bondState);
             return false;
         }
-
-        Log.d(TAG, "Bond " + device.getName() + " by CSIP");
-        mOngoingSetMemberPair = device;
         return true;
     }
 
     /**
+     * Called when we found a set member of a group. The function will check the {@code groupId} if
+     * it exists and the bond state of the device is BOND_NONE, and if there isn't any ongoing pair
+     * , and then pair the device automatically.
+     *
+     * @param device The found device
+     * @param groupId The group id of the found device
+     */
+    public synchronized void pairDeviceByCsip(BluetoothDevice device, int groupId) {
+        if (!shouldPairByCsip(device, groupId)) {
+            return;
+        }
+        Log.d(TAG, "Bond " + device.getAnonymizedAddress() + " by CSIP");
+        mOngoingSetMemberPair = device;
+        syncConfigFromMainDevice(device, groupId);
+        device.createBond(BluetoothDevice.TRANSPORT_LE);
+    }
+
+    private void syncConfigFromMainDevice(BluetoothDevice device, int groupId) {
+        if (!isOngoingPairByCsip(device)) {
+            return;
+        }
+        CachedBluetoothDevice memberDevice = findDevice(device);
+        CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(memberDevice);
+        if (mainDevice == null) {
+            mainDevice = mCsipDeviceManager.getCachedDevice(groupId);
+        }
+
+        if (mainDevice == null || mainDevice.equals(memberDevice)) {
+            Log.d(TAG, "no mainDevice");
+            return;
+        }
+
+        // The memberDevice set PhonebookAccessPermission
+        device.setPhonebookAccessPermission(mainDevice.getDevice().getPhonebookAccessPermission());
+    }
+
+    /**
      * Called when the bond state change. If the bond state change is related with the
      * ongoing set member pair, the cachedBluetoothDevice will be created but the UI
      * would not be updated. For the other case, return {@code false} to go through the normal
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index d5de3f0..20a6cd8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -101,7 +101,14 @@
         return groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
     }
 
-    private CachedBluetoothDevice getCachedDevice(int groupId) {
+    /**
+     * To find the device with {@code groupId}.
+     *
+     * @param groupId The group id
+     * @return if we could find a device with this {@code groupId} return this device. Otherwise,
+     * return null.
+     */
+    public CachedBluetoothDevice getCachedDevice(int groupId) {
         log("getCachedDevice: groupId: " + groupId);
         for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
             CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java
index 39977df..f969a63 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java
@@ -41,19 +41,19 @@
         MobileNetworkTypeIcon icon =
                 MobileNetworkTypeIcons.getNetworkTypeIcon(TelephonyIcons.FOUR_G);
 
-        assertThat(icon.getName()).isEqualTo(TelephonyIcons.H_PLUS.name);
+        assertThat(icon.getName()).isEqualTo(TelephonyIcons.FOUR_G.name);
         assertThat(icon.getIconResId()).isEqualTo(TelephonyIcons.ICON_4G);
     }
 
     @Test
     public void getNetworkTypeIcon_unknown_returnsUnknown() {
-        SignalIcon.MobileIconGroup unknownGroup =
-                new SignalIcon.MobileIconGroup("testUnknownNameHere", 45, 6);
+        SignalIcon.MobileIconGroup unknownGroup = new SignalIcon.MobileIconGroup(
+                "testUnknownNameHere", /* dataContentDesc= */ 45, /* dataType= */ 6);
 
         MobileNetworkTypeIcon icon = MobileNetworkTypeIcons.getNetworkTypeIcon(unknownGroup);
 
         assertThat(icon.getName()).isEqualTo("testUnknownNameHere");
-        assertThat(icon.getIconResId()).isEqualTo(45);
-        assertThat(icon.getContentDescriptionResId()).isEqualTo(6);
+        assertThat(icon.getIconResId()).isEqualTo(6);
+        assertThat(icon.getContentDescriptionResId()).isEqualTo(45);
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
index 62552f91..61802a8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -582,4 +582,24 @@
         assertThat(mCachedDeviceManager.isSubDevice(mDevice2)).isTrue();
         assertThat(mCachedDeviceManager.isSubDevice(mDevice3)).isFalse();
     }
+
+    @Test
+    public void pairDeviceByCsip_device2AndCapGroup1_device2StartsPairing() {
+        doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice1);
+        when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mDevice1.getPhonebookAccessPermission()).thenReturn(BluetoothDevice.ACCESS_ALLOWED);
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+        assertThat(cachedDevice1).isNotNull();
+        when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+        assertThat(cachedDevice2).isNotNull();
+
+        int groupId = CAP_GROUP1.keySet().stream().findFirst().orElse(
+                BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
+        assertThat(groupId).isNotEqualTo(BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
+        mCachedDeviceManager.pairDeviceByCsip(mDevice2, groupId);
+
+        verify(mDevice2).setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
+        verify(mDevice2).createBond(BluetoothDevice.TRANSPORT_LE);
+    }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index fd7554f..528af2e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -376,9 +376,11 @@
             Setting newSetting = new Setting(name, oldSetting.getValue(), null,
                     oldSetting.getPackageName(), oldSetting.getTag(), false,
                     oldSetting.getId());
-            mSettings.put(name, newSetting);
-            updateMemoryUsagePerPackageLocked(newSetting.getPackageName(), oldValue,
+            int newSize = getNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), oldValue,
                     newSetting.getValue(), oldDefaultValue, newSetting.getDefaultValue());
+            checkNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), newSize);
+            mSettings.put(name, newSetting);
+            updateMemoryUsagePerPackageLocked(newSetting.getPackageName(), newSize);
             scheduleWriteIfNeededLocked();
         }
     }
@@ -410,6 +412,12 @@
         Setting oldState = mSettings.get(name);
         String oldValue = (oldState != null) ? oldState.value : null;
         String oldDefaultValue = (oldState != null) ? oldState.defaultValue : null;
+        String newDefaultValue = makeDefault ? value : oldDefaultValue;
+
+        int newSize = getNewMemoryUsagePerPackageLocked(packageName, oldValue, value,
+                oldDefaultValue, newDefaultValue);
+        checkNewMemoryUsagePerPackageLocked(packageName, newSize);
+
         Setting newState;
 
         if (oldState != null) {
@@ -430,8 +438,7 @@
 
         addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, newState);
 
-        updateMemoryUsagePerPackageLocked(packageName, oldValue, value,
-                oldDefaultValue, newState.getDefaultValue());
+        updateMemoryUsagePerPackageLocked(packageName, newSize);
 
         scheduleWriteIfNeededLocked();
 
@@ -552,13 +559,14 @@
         }
 
         Setting oldState = mSettings.remove(name);
+        int newSize = getNewMemoryUsagePerPackageLocked(oldState.packageName, oldState.value,
+                null, oldState.defaultValue, null);
 
         FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, name, /* value= */ "",
                 /* newValue= */ "", oldState.value, /* tag */ "", false, getUserIdFromKey(mKey),
                 FrameworkStatsLog.SETTING_CHANGED__REASON__DELETED);
 
-        updateMemoryUsagePerPackageLocked(oldState.packageName, oldState.value,
-                null, oldState.defaultValue, null);
+        updateMemoryUsagePerPackageLocked(oldState.packageName, newSize);
 
         addHistoricalOperationLocked(HISTORICAL_OPERATION_DELETE, oldState);
 
@@ -579,16 +587,18 @@
         Setting oldSetting = new Setting(setting);
         String oldValue = setting.getValue();
         String oldDefaultValue = setting.getDefaultValue();
+        String newValue = oldDefaultValue;
+        String newDefaultValue = oldDefaultValue;
+
+        int newSize = getNewMemoryUsagePerPackageLocked(setting.packageName, oldValue,
+                newValue, oldDefaultValue, newDefaultValue);
+        checkNewMemoryUsagePerPackageLocked(setting.packageName, newSize);
 
         if (!setting.reset()) {
             return false;
         }
 
-        String newValue = setting.getValue();
-        String newDefaultValue = setting.getDefaultValue();
-
-        updateMemoryUsagePerPackageLocked(setting.packageName, oldValue,
-                newValue, oldDefaultValue, newDefaultValue);
+        updateMemoryUsagePerPackageLocked(setting.packageName, newSize);
 
         addHistoricalOperationLocked(HISTORICAL_OPERATION_RESET, oldSetting);
 
@@ -696,38 +706,49 @@
     }
 
     @GuardedBy("mLock")
-    private void updateMemoryUsagePerPackageLocked(String packageName, String oldValue,
+    private boolean isExemptFromMemoryUsageCap(String packageName) {
+        return mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED
+                || SYSTEM_PACKAGE_NAME.equals(packageName);
+    }
+
+    @GuardedBy("mLock")
+    private void checkNewMemoryUsagePerPackageLocked(String packageName, int newSize)
+            throws IllegalStateException {
+        if (isExemptFromMemoryUsageCap(packageName)) {
+            return;
+        }
+        if (newSize > mMaxBytesPerAppPackage) {
+            throw new IllegalStateException("You are adding too many system settings. "
+                    + "You should stop using system settings for app specific data"
+                    + " package: " + packageName);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private int getNewMemoryUsagePerPackageLocked(String packageName, String oldValue,
             String newValue, String oldDefaultValue, String newDefaultValue) {
-        if (mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED) {
-            return;
+        if (isExemptFromMemoryUsageCap(packageName)) {
+            return 0;
         }
-
-        if (SYSTEM_PACKAGE_NAME.equals(packageName)) {
-            return;
-        }
-
+        final Integer currentSize = mPackageToMemoryUsage.get(packageName);
         final int oldValueSize = (oldValue != null) ? oldValue.length() : 0;
         final int newValueSize = (newValue != null) ? newValue.length() : 0;
         final int oldDefaultValueSize = (oldDefaultValue != null) ? oldDefaultValue.length() : 0;
         final int newDefaultValueSize = (newDefaultValue != null) ? newDefaultValue.length() : 0;
         final int deltaSize = newValueSize + newDefaultValueSize
                 - oldValueSize - oldDefaultValueSize;
+        return Math.max((currentSize != null) ? currentSize + deltaSize : deltaSize, 0);
+    }
 
-        Integer currentSize = mPackageToMemoryUsage.get(packageName);
-        final int newSize = Math.max((currentSize != null)
-                ? currentSize + deltaSize : deltaSize, 0);
-
-        if (newSize > mMaxBytesPerAppPackage) {
-            throw new IllegalStateException("You are adding too many system settings. "
-                    + "You should stop using system settings for app specific data"
-                    + " package: " + packageName);
+    @GuardedBy("mLock")
+    private void updateMemoryUsagePerPackageLocked(String packageName, int newSize) {
+        if (isExemptFromMemoryUsageCap(packageName)) {
+            return;
         }
-
         if (DEBUG) {
             Slog.i(LOG_TAG, "Settings for package: " + packageName
                     + " size: " + newSize + " bytes.");
         }
-
         mPackageToMemoryUsage.put(packageName, newSize);
     }
 
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 69eb713..66b809a 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -20,6 +20,8 @@
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
 
+import com.google.common.base.Strings;
+
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
@@ -276,4 +278,40 @@
         settingsState.setVersionLocked(SettingsState.SETTINGS_VERSION_NEW_ENCODING);
         return settingsState;
     }
+
+    public void testInsertSetting_memoryUsage() {
+        SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
+                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+        // No exception should be thrown when there is no cap
+        settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
+                null, false, "p1");
+        settingsState.deleteSettingLocked(SETTING_NAME);
+
+        settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
+                SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
+        // System package doesn't have memory usage limit
+        settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
+                null, false, SYSTEM_PACKAGE);
+        settingsState.deleteSettingLocked(SETTING_NAME);
+
+        // Should not throw if usage is under the cap
+        settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 19999),
+                null, false, "p1");
+        settingsState.deleteSettingLocked(SETTING_NAME);
+        try {
+            settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
+                    null, false, "p1");
+            fail("Should throw because it exceeded per package memory usage");
+        } catch (IllegalStateException ex) {
+            assertTrue(ex.getMessage().contains("p1"));
+        }
+        try {
+            settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
+                    null, false, "p1");
+            fail("Should throw because it exceeded per package memory usage");
+        } catch (IllegalStateException ex) {
+            assertTrue(ex.getMessage().contains("p1"));
+        }
+        assertTrue(settingsState.getSettingLocked(SETTING_NAME).isNull());
+    }
 }
diff --git a/packages/SoundPicker/res/values-am/strings.xml b/packages/SoundPicker/res/values-am/strings.xml
index 07aee8a..85206c0 100644
--- a/packages/SoundPicker/res/values-am/strings.xml
+++ b/packages/SoundPicker/res/values-am/strings.xml
@@ -17,7 +17,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="ringtone_default" msgid="798836092118824500">"ነባሪ የስልክ ላይ ጥሪ"</string>
-    <string name="notification_sound_default" msgid="8133121186242636840">"ነባሪ የማሳወቂያ ድምጽ"</string>
+    <string name="notification_sound_default" msgid="8133121186242636840">"ነባሪ የማሳወቂያ ድምፅ"</string>
     <string name="alarm_sound_default" msgid="4787646764557462649">"ነባሪ የማንቂያ ድምፅ"</string>
     <string name="add_ringtone_text" msgid="6642389991738337529">"የጥሪ ቅላጼ አክል"</string>
     <string name="add_alarm_text" msgid="3545497316166999225">"የማንቂያ ደውል አክል"</string>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 2737ecf..b5145f9 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -402,6 +402,9 @@
                  android:permission="com.android.systemui.permission.SELF"
                  android:exported="false" />
 
+        <service android:name=".screenshot.ScreenshotCrossProfileService"
+                 android:permission="com.android.systemui.permission.SELF"
+                 android:exported="false" />
 
         <service android:name=".screenrecord.RecordingService" />
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 23cee4d..fdfad2b 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -25,7 +25,6 @@
 import android.os.Looper
 import android.util.Log
 import android.util.MathUtils
-import android.view.GhostView
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
@@ -86,6 +85,9 @@
          */
         val sourceIdentity: Any
 
+        /** The CUJ associated to this controller. */
+        val cuj: DialogCuj?
+
         /**
          * Move the drawing of the source in the overlay of [viewGroup].
          *
@@ -142,7 +144,31 @@
          * controlled by this controller.
          */
         // TODO(b/252723237): Make this non-nullable
-        fun jankConfigurationBuilder(cuj: Int): InteractionJankMonitor.Configuration.Builder?
+        fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder?
+
+        companion object {
+            /**
+             * Create a [Controller] that can animate [source] to and from a dialog.
+             *
+             * Important: The view must be attached to a [ViewGroup] when calling this function and
+             * during the animation. For safety, this method will return null when it is not.
+             *
+             * Note: The background of [view] should be a (rounded) rectangle so that it can be
+             * properly animated.
+             */
+            fun fromView(source: View, cuj: DialogCuj? = null): Controller? {
+                if (source.parent !is ViewGroup) {
+                    Log.e(
+                        TAG,
+                        "Skipping animation as view $source is not attached to a ViewGroup",
+                        Exception(),
+                    )
+                    return null
+                }
+
+                return ViewDialogLaunchAnimatorController(source, cuj)
+            }
+        }
     }
 
     /**
@@ -172,7 +198,12 @@
         cuj: DialogCuj? = null,
         animateBackgroundBoundsChange: Boolean = false
     ) {
-        show(dialog, createController(view), cuj, animateBackgroundBoundsChange)
+        val controller = Controller.fromView(view, cuj)
+        if (controller == null) {
+            dialog.show()
+        } else {
+            show(dialog, controller, animateBackgroundBoundsChange)
+        }
     }
 
     /**
@@ -187,10 +218,10 @@
      * Caveats: When calling this function and [dialog] is not a fullscreen dialog, then it will be
      * made fullscreen and 2 views will be inserted between the dialog DecorView and its children.
      */
+    @JvmOverloads
     fun show(
         dialog: Dialog,
         controller: Controller,
-        cuj: DialogCuj? = null,
         animateBackgroundBoundsChange: Boolean = false
     ) {
         if (Looper.myLooper() != Looper.getMainLooper()) {
@@ -207,7 +238,10 @@
                 it.dialog.window.decorView.viewRootImpl == controller.viewRoot
             }
         val animateFrom =
-            animatedParent?.dialogContentWithBackground?.let { createController(it) } ?: controller
+            animatedParent?.dialogContentWithBackground?.let {
+                Controller.fromView(it, controller.cuj)
+            }
+                ?: controller
 
         if (animatedParent == null && animateFrom !is LaunchableView) {
             // Make sure the View we launch from implements LaunchableView to avoid visibility
@@ -244,96 +278,12 @@
                 animateBackgroundBoundsChange,
                 animatedParent,
                 isForTesting,
-                cuj,
             )
 
         openedDialogs.add(animatedDialog)
         animatedDialog.start()
     }
 
-    /** Create a [Controller] that can animate [source] to & from a dialog. */
-    private fun createController(source: View): Controller {
-        return object : Controller {
-            override val viewRoot: ViewRootImpl
-                get() = source.viewRootImpl
-
-            override val sourceIdentity: Any = source
-
-            override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
-                // Create a temporary ghost of the source (which will make it invisible) and add it
-                // to the host dialog.
-                GhostView.addGhost(source, viewGroup)
-
-                // The ghost of the source was just created, so the source is currently invisible.
-                // We need to make sure that it stays invisible as long as the dialog is shown or
-                // animating.
-                (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
-            }
-
-            override fun stopDrawingInOverlay() {
-                // Note: here we should remove the ghost from the overlay, but in practice this is
-                // already done by the launch controllers created below.
-
-                // Make sure we allow the source to change its visibility again.
-                (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
-                source.visibility = View.VISIBLE
-            }
-
-            override fun createLaunchController(): LaunchAnimator.Controller {
-                val delegate = GhostedViewLaunchAnimatorController(source)
-                return object : LaunchAnimator.Controller by delegate {
-                    override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
-                        // Remove the temporary ghost added by [startDrawingInOverlayOf]. Another
-                        // ghost (that ghosts only the source content, and not its background) will
-                        // be added right after this by the delegate and will be animated.
-                        GhostView.removeGhost(source)
-                        delegate.onLaunchAnimationStart(isExpandingFullyAbove)
-                    }
-
-                    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
-                        delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
-
-                        // We hide the source when the dialog is showing. We will make this view
-                        // visible again when dismissing the dialog. This does nothing if the source
-                        // implements [LaunchableView], as it's already INVISIBLE in that case.
-                        source.visibility = View.INVISIBLE
-                    }
-                }
-            }
-
-            override fun createExitController(): LaunchAnimator.Controller {
-                return GhostedViewLaunchAnimatorController(source)
-            }
-
-            override fun shouldAnimateExit(): Boolean {
-                // The source should be invisible by now, if it's not then something else changed
-                // its visibility and we probably don't want to run the animation.
-                if (source.visibility != View.INVISIBLE) {
-                    return false
-                }
-
-                return source.isAttachedToWindow && ((source.parent as? View)?.isShown ?: true)
-            }
-
-            override fun onExitAnimationCancelled() {
-                // Make sure we allow the source to change its visibility again.
-                (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
-
-                // If the view is invisible it's probably because of us, so we make it visible
-                // again.
-                if (source.visibility == View.INVISIBLE) {
-                    source.visibility = View.VISIBLE
-                }
-            }
-
-            override fun jankConfigurationBuilder(
-                cuj: Int
-            ): InteractionJankMonitor.Configuration.Builder? {
-                return InteractionJankMonitor.Configuration.Builder.withView(cuj, source)
-            }
-        }
-    }
-
     /**
      * Launch [dialog] from [another dialog][animateFrom] that was shown using [show]. This will
      * allow for dismissing the whole stack.
@@ -563,9 +513,6 @@
      * Whether synchronization should be disabled, which can be useful if we are running in a test.
      */
     private val forceDisableSynchronization: Boolean,
-
-    /** Interaction to which the dialog animation is associated. */
-    private val cuj: DialogCuj? = null
 ) {
     /**
      * The DecorView of this dialog window.
@@ -618,8 +565,9 @@
     private var hasInstrumentedJank = false
 
     fun start() {
+        val cuj = controller.cuj
         if (cuj != null) {
-            val config = controller.jankConfigurationBuilder(cuj.cujType)
+            val config = controller.jankConfigurationBuilder()
             if (config != null) {
                 if (cuj.tag != null) {
                     config.setTag(cuj.tag)
@@ -865,7 +813,7 @@
             return
         }
 
-        ViewRootSync.synchronizeNextDraw(decorView, controller.viewRoot.view, then)
+        ViewRootSync.synchronizeNextDraw(controller.viewRoot.view, decorView, then)
         decorView.invalidate()
         controller.viewRoot.view.invalidate()
     }
@@ -917,7 +865,7 @@
                 }
 
                 if (hasInstrumentedJank) {
-                    interactionJankMonitor.end(cuj!!.cujType)
+                    interactionJankMonitor.end(controller.cuj!!.cujType)
                 }
             }
         )
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
index 8ce372d..40a5e97 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
@@ -30,7 +30,12 @@
      */
     fun activityLaunchController(cujType: Int? = null): ActivityLaunchAnimator.Controller?
 
-    // TODO(b/230830644): Introduce DialogLaunchAnimator and a function to expose it here.
+    /**
+     * Create a [DialogLaunchAnimator.Controller] that can be used to expand this [Expandable] into
+     * a Dialog, or return `null` if this [Expandable] should not be animated (e.g. if it is
+     * currently not attached or visible).
+     */
+    fun dialogLaunchController(cuj: DialogCuj? = null): DialogLaunchAnimator.Controller?
 
     companion object {
         /**
@@ -39,6 +44,7 @@
          * Note: The background of [view] should be a (rounded) rectangle so that it can be properly
          * animated.
          */
+        @JvmStatic
         fun fromView(view: View): Expandable {
             return object : Expandable {
                 override fun activityLaunchController(
@@ -46,6 +52,12 @@
                 ): ActivityLaunchAnimator.Controller? {
                     return ActivityLaunchAnimator.Controller.fromView(view, cujType)
                 }
+
+                override fun dialogLaunchController(
+                    cuj: DialogCuj?
+                ): DialogLaunchAnimator.Controller? {
+                    return DialogLaunchAnimator.Controller.fromView(view, cuj)
+                }
             }
         }
     }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index f79b328..5f1bb83 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -89,6 +89,11 @@
         var y: Float = 0f
 
         /**
+         * The current line of text being drawn, in a multi-line TextView.
+         */
+        var lineNo: Int = 0
+
+        /**
          * Mutable text size of the glyph in pixels.
          */
         var textSize: Float = 0f
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index d427a57..0448c81 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -244,7 +244,7 @@
                     canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat())
 
                     run.fontRuns.forEach { fontRun ->
-                        drawFontRun(canvas, run, fontRun, tmpPaint)
+                        drawFontRun(canvas, run, fontRun, lineNo, tmpPaint)
                     }
                 } finally {
                     canvas.restore()
@@ -349,7 +349,7 @@
     var glyphFilter: GlyphCallback? = null
 
     // Draws single font run.
-    private fun drawFontRun(c: Canvas, line: Run, run: FontRun, paint: Paint) {
+    private fun drawFontRun(c: Canvas, line: Run, run: FontRun, lineNo: Int, paint: Paint) {
         var arrayIndex = 0
         val font = fontInterpolator.lerp(run.baseFont, run.targetFont, progress)
 
@@ -368,11 +368,13 @@
         tmpGlyph.font = font
         tmpGlyph.runStart = run.start
         tmpGlyph.runLength = run.end - run.start
+        tmpGlyph.lineNo = lineNo
 
         tmpPaintForGlyph.set(paint)
         var prevStart = run.start
 
         for (i in run.start until run.end) {
+            tmpGlyph.glyphIndex = i
             tmpGlyph.glyphId = line.glyphIds[i]
             tmpGlyph.x = MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
             tmpGlyph.y = MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
new file mode 100644
index 0000000..ecee598
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.animation
+
+import android.view.GhostView
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewRootImpl
+import com.android.internal.jank.InteractionJankMonitor
+
+/** A [DialogLaunchAnimator.Controller] that can animate a [View] from/to a dialog. */
+class ViewDialogLaunchAnimatorController
+internal constructor(
+    private val source: View,
+    override val cuj: DialogCuj?,
+) : DialogLaunchAnimator.Controller {
+    override val viewRoot: ViewRootImpl
+        get() = source.viewRootImpl
+
+    override val sourceIdentity: Any = source
+
+    override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
+        // Create a temporary ghost of the source (which will make it invisible) and add it
+        // to the host dialog.
+        GhostView.addGhost(source, viewGroup)
+
+        // The ghost of the source was just created, so the source is currently invisible.
+        // We need to make sure that it stays invisible as long as the dialog is shown or
+        // animating.
+        (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
+    }
+
+    override fun stopDrawingInOverlay() {
+        // Note: here we should remove the ghost from the overlay, but in practice this is
+        // already done by the launch controllers created below.
+
+        // Make sure we allow the source to change its visibility again.
+        (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
+        source.visibility = View.VISIBLE
+    }
+
+    override fun createLaunchController(): LaunchAnimator.Controller {
+        val delegate = GhostedViewLaunchAnimatorController(source)
+        return object : LaunchAnimator.Controller by delegate {
+            override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+                // Remove the temporary ghost added by [startDrawingInOverlayOf]. Another
+                // ghost (that ghosts only the source content, and not its background) will
+                // be added right after this by the delegate and will be animated.
+                GhostView.removeGhost(source)
+                delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+            }
+
+            override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+                delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+
+                // We hide the source when the dialog is showing. We will make this view
+                // visible again when dismissing the dialog. This does nothing if the source
+                // implements [LaunchableView], as it's already INVISIBLE in that case.
+                source.visibility = View.INVISIBLE
+            }
+        }
+    }
+
+    override fun createExitController(): LaunchAnimator.Controller {
+        return GhostedViewLaunchAnimatorController(source)
+    }
+
+    override fun shouldAnimateExit(): Boolean {
+        // The source should be invisible by now, if it's not then something else changed
+        // its visibility and we probably don't want to run the animation.
+        if (source.visibility != View.INVISIBLE) {
+            return false
+        }
+
+        return source.isAttachedToWindow && ((source.parent as? View)?.isShown ?: true)
+    }
+
+    override fun onExitAnimationCancelled() {
+        // Make sure we allow the source to change its visibility again.
+        (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
+
+        // If the view is invisible it's probably because of us, so we make it visible
+        // again.
+        if (source.visibility == View.INVISIBLE) {
+            source.visibility = View.VISIBLE
+        }
+    }
+
+    override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? {
+        val type = cuj?.cujType ?: return null
+        return InteractionJankMonitor.Configuration.Builder.withView(type, source)
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index 1b7e26b..58ffef2 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -360,7 +360,9 @@
          * [interpolator] and [duration].
          *
          * The end state of the animation is controlled by [destination]. This value can be any of
-         * the four corners, any of the four edges, or the center of the view.
+         * the four corners, any of the four edges, or the center of the view. If any margins are
+         * added on the side(s) of the [destination], the translation of those margins can be
+         * included by specifying [includeMargins].
          *
          * @param onAnimationEnd an optional runnable that will be run once the animation finishes
          *    successfully. Will not be run if the animation is cancelled.
@@ -371,6 +373,7 @@
             destination: Hotspot = Hotspot.CENTER,
             interpolator: Interpolator = DEFAULT_REMOVAL_INTERPOLATOR,
             duration: Long = DEFAULT_DURATION,
+            includeMargins: Boolean = false,
             onAnimationEnd: Runnable? = null,
         ): Boolean {
             if (
@@ -428,10 +431,12 @@
             val endValues =
                 processEndValuesForRemoval(
                     destination,
+                    rootView,
                     rootView.left,
                     rootView.top,
                     rootView.right,
-                    rootView.bottom
+                    rootView.bottom,
+                    includeMargins,
                 )
 
             val boundsToAnimate = mutableSetOf<Bound>()
@@ -718,70 +723,111 @@
          *         |         | ->  |       |  ->   |     |   ->    x---x    ->      x
          *         |         |     x-------x       x-----x
          *         x---------x
+         *     4) destination=TOP, includeMargins=true (and view has large top margin)
+         *                                                                     x---------x
+         *                                                      x---------x
+         *                                       x---------x    x---------x
+         *                        x---------x    |         |
+         *         x---------x    |         |    x---------x
+         *         |         |    |         |
+         *         |         | -> x---------x ->             ->             ->
+         *         |         |
+         *         x---------x
          * ```
          */
         private fun processEndValuesForRemoval(
             destination: Hotspot,
+            rootView: View,
             left: Int,
             top: Int,
             right: Int,
-            bottom: Int
+            bottom: Int,
+            includeMargins: Boolean = false,
         ): Map<Bound, Int> {
-            val endLeft =
-                when (destination) {
-                    Hotspot.CENTER -> (left + right) / 2
-                    Hotspot.BOTTOM,
-                    Hotspot.BOTTOM_LEFT,
-                    Hotspot.LEFT,
-                    Hotspot.TOP_LEFT,
-                    Hotspot.TOP -> left
-                    Hotspot.TOP_RIGHT,
-                    Hotspot.RIGHT,
-                    Hotspot.BOTTOM_RIGHT -> right
-                }
-            val endTop =
-                when (destination) {
-                    Hotspot.CENTER -> (top + bottom) / 2
-                    Hotspot.LEFT,
-                    Hotspot.TOP_LEFT,
-                    Hotspot.TOP,
-                    Hotspot.TOP_RIGHT,
-                    Hotspot.RIGHT -> top
-                    Hotspot.BOTTOM_RIGHT,
-                    Hotspot.BOTTOM,
-                    Hotspot.BOTTOM_LEFT -> bottom
-                }
-            val endRight =
-                when (destination) {
-                    Hotspot.CENTER -> (left + right) / 2
-                    Hotspot.TOP,
-                    Hotspot.TOP_RIGHT,
-                    Hotspot.RIGHT,
-                    Hotspot.BOTTOM_RIGHT,
-                    Hotspot.BOTTOM -> right
-                    Hotspot.BOTTOM_LEFT,
-                    Hotspot.LEFT,
-                    Hotspot.TOP_LEFT -> left
-                }
-            val endBottom =
-                when (destination) {
-                    Hotspot.CENTER -> (top + bottom) / 2
-                    Hotspot.RIGHT,
-                    Hotspot.BOTTOM_RIGHT,
-                    Hotspot.BOTTOM,
-                    Hotspot.BOTTOM_LEFT,
-                    Hotspot.LEFT -> bottom
-                    Hotspot.TOP_LEFT,
-                    Hotspot.TOP,
-                    Hotspot.TOP_RIGHT -> top
-                }
+            val marginAdjustment =
+                if (includeMargins &&
+                    (rootView.layoutParams is ViewGroup.MarginLayoutParams)) {
+                    val marginLp = rootView.layoutParams as ViewGroup.MarginLayoutParams
+                    DimenHolder(
+                        left = marginLp.leftMargin,
+                        top = marginLp.topMargin,
+                        right = marginLp.rightMargin,
+                        bottom = marginLp.bottomMargin
+                    )
+            } else {
+                DimenHolder(0, 0, 0, 0)
+            }
 
-            return mapOf(
-                Bound.LEFT to endLeft,
-                Bound.TOP to endTop,
-                Bound.RIGHT to endRight,
-                Bound.BOTTOM to endBottom
-            )
+            // These are the end values to use *if* this bound is part of the destination.
+            val endLeft = left - marginAdjustment.left
+            val endTop = top - marginAdjustment.top
+            val endRight = right + marginAdjustment.right
+            val endBottom = bottom + marginAdjustment.bottom
+
+            // For the below calculations: We need to ensure that the destination bound and the
+            // bound *opposite* to the destination bound end at the same value, to ensure that the
+            // view has size 0 for that dimension.
+            // For example,
+            //  - If destination=TOP, then endTop == endBottom. Left and right stay the same.
+            //  - If destination=RIGHT, then endRight == endLeft. Top and bottom stay the same.
+            //  - If destination=BOTTOM_LEFT, then endBottom == endTop AND endLeft == endRight.
+
+            return when (destination) {
+                Hotspot.TOP -> mapOf(
+                    Bound.TOP to endTop,
+                    Bound.BOTTOM to endTop,
+                    Bound.LEFT to left,
+                    Bound.RIGHT to right,
+                )
+                Hotspot.TOP_RIGHT -> mapOf(
+                    Bound.TOP to endTop,
+                    Bound.BOTTOM to endTop,
+                    Bound.RIGHT to endRight,
+                    Bound.LEFT to endRight,
+                )
+                Hotspot.RIGHT -> mapOf(
+                    Bound.RIGHT to endRight,
+                    Bound.LEFT to endRight,
+                    Bound.TOP to top,
+                    Bound.BOTTOM to bottom,
+                )
+                Hotspot.BOTTOM_RIGHT -> mapOf(
+                    Bound.BOTTOM to endBottom,
+                    Bound.TOP to endBottom,
+                    Bound.RIGHT to endRight,
+                    Bound.LEFT to endRight,
+                )
+                Hotspot.BOTTOM -> mapOf(
+                    Bound.BOTTOM to endBottom,
+                    Bound.TOP to endBottom,
+                    Bound.LEFT to left,
+                    Bound.RIGHT to right,
+                )
+                Hotspot.BOTTOM_LEFT -> mapOf(
+                    Bound.BOTTOM to endBottom,
+                    Bound.TOP to endBottom,
+                    Bound.LEFT to endLeft,
+                    Bound.RIGHT to endLeft,
+                )
+                Hotspot.LEFT -> mapOf(
+                    Bound.LEFT to endLeft,
+                    Bound.RIGHT to endLeft,
+                    Bound.TOP to top,
+                    Bound.BOTTOM to bottom,
+                )
+                Hotspot.TOP_LEFT -> mapOf(
+                    Bound.TOP to endTop,
+                    Bound.BOTTOM to endTop,
+                    Bound.LEFT to endLeft,
+                    Bound.RIGHT to endLeft,
+                )
+                Hotspot.CENTER -> mapOf(
+                    Bound.LEFT to (endLeft + endRight) / 2,
+                    Bound.RIGHT to (endLeft + endRight) / 2,
+                    Bound.TOP to (endTop + endBottom) / 2,
+                    Bound.BOTTOM to (endTop + endBottom) / 2,
+                )
+            }
         }
 
         /**
@@ -1061,4 +1107,12 @@
         abstract fun setValue(view: View, value: Int)
         abstract fun getValue(view: View): Int
     }
+
+    /** Simple data class to hold a set of dimens for left, top, right, bottom. */
+    private data class DimenHolder(
+        val left: Int,
+        val top: Int,
+        val right: Int,
+        val bottom: Int,
+    )
 }
diff --git a/packages/SystemUI/checks/Android.bp b/packages/SystemUI/checks/Android.bp
index 8457312..cf66ff6 100644
--- a/packages/SystemUI/checks/Android.bp
+++ b/packages/SystemUI/checks/Android.bp
@@ -40,6 +40,10 @@
         "tests/**/*.kt",
         "tests/**/*.java",
     ],
+    data: [
+        ":framework",
+        ":androidx.annotation_annotation",
+    ],
     static_libs: [
         "SystemUILintChecker",
         "junit",
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
index 4eeeb85..4b9aa13 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
@@ -32,7 +32,8 @@
 class SoftwareBitmapDetector : Detector(), SourceCodeScanner {
 
     override fun getApplicableReferenceNames(): List<String> {
-        return mutableListOf("ALPHA_8", "RGB_565", "ARGB_8888", "RGBA_F16", "RGBA_1010102")
+        return mutableListOf(
+            "ALPHA_8", "RGB_565", "ARGB_4444", "ARGB_8888", "RGBA_F16", "RGBA_1010102")
     }
 
     override fun visitReference(
@@ -40,13 +41,12 @@
             reference: UReferenceExpression,
             referenced: PsiElement
     ) {
-
         val evaluator = context.evaluator
         if (evaluator.isMemberInClass(referenced as? PsiField, "android.graphics.Bitmap.Config")) {
             context.report(
                     ISSUE,
                     referenced,
-                    context.getNameLocation(referenced),
+                    context.getNameLocation(reference),
                     "Replace software bitmap with `Config.HARDWARE`"
             )
         }
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt
new file mode 100644
index 0000000..1db0725
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+private const val CLASS_SETTINGS = "android.provider.Settings"
+
+/**
+ * Detects usage of static methods in android.provider.Settings and suggests to use an injected
+ * settings provider instance instead.
+ */
+@Suppress("UnstableApiUsage")
+class StaticSettingsProviderDetector : Detector(), SourceCodeScanner {
+    override fun getApplicableMethodNames(): List<String> {
+        return listOf(
+            "getFloat",
+            "getInt",
+            "getLong",
+            "getString",
+            "getUriFor",
+            "putFloat",
+            "putInt",
+            "putLong",
+            "putString"
+        )
+    }
+
+    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+        val evaluator = context.evaluator
+        val className = method.containingClass?.qualifiedName
+        if (
+            className != "$CLASS_SETTINGS.Global" &&
+                className != "$CLASS_SETTINGS.Secure" &&
+                className != "$CLASS_SETTINGS.System"
+        ) {
+            return
+        }
+        if (!evaluator.isStatic(method)) {
+            return
+        }
+
+        val subclassName = className.substring(CLASS_SETTINGS.length + 1)
+
+        context.report(
+            ISSUE,
+            method,
+            context.getNameLocation(node),
+            "`@Inject` a ${subclassName}Settings instead"
+        )
+    }
+
+    companion object {
+        @JvmField
+        val ISSUE: Issue =
+            Issue.create(
+                id = "StaticSettingsProvider",
+                briefDescription = "Static settings provider usage",
+                explanation =
+                    """
+                    Static settings provider methods, such as `Settings.Global.putInt()`, should \
+                    not be used because they make testing difficult. Instead, use an injected \
+                    settings provider. For example, instead of calling `Settings.Secure.getInt()`, \
+                    annotate the class constructor with `@Inject` and add `SecureSettings` to the \
+                    parameters.
+                    """,
+                category = Category.CORRECTNESS,
+                priority = 8,
+                severity = Severity.WARNING,
+                implementation =
+                    Implementation(
+                        StaticSettingsProviderDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                    )
+            )
+    }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index cf7c1b5..3f334c1c 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -36,6 +36,7 @@
                 RegisterReceiverViaContextDetector.ISSUE,
                 SoftwareBitmapDetector.ISSUE,
                 NonInjectedServiceDetector.ISSUE,
+                StaticSettingsProviderDetector.ISSUE
         )
 
     override val api: Int
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
index 486af9d..141dd05 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
@@ -18,6 +18,8 @@
 
 import com.android.annotations.NonNull
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java
+import com.android.tools.lint.checks.infrastructure.TestFiles.LibraryReferenceTestFile
+import java.io.File
 import org.intellij.lang.annotations.Language
 
 @Suppress("UnstableApiUsage")
@@ -30,132 +32,8 @@
  */
 internal val androidStubs =
     arrayOf(
-        indentedJava(
-            """
-package android.app;
-
-public class ActivityManager {
-    public static int getCurrentUser() {}
-}
-"""
-        ),
-        indentedJava(
-            """
-package android.accounts;
-
-public class AccountManager {
-    public static AccountManager get(Context context) { return null; }
-}
-"""
-        ),
-        indentedJava(
-            """
-package android.os;
-import android.content.pm.UserInfo;
-import android.annotation.UserIdInt;
-
-public class UserManager {
-    public UserInfo getUserInfo(@UserIdInt int userId) {}
-}
-"""
-        ),
-        indentedJava("""
-package android.annotation;
-
-public @interface UserIdInt {}
-"""),
-        indentedJava("""
-package android.content.pm;
-
-public class UserInfo {}
-"""),
-        indentedJava("""
-package android.os;
-
-public class Looper {}
-"""),
-        indentedJava("""
-package android.os;
-
-public class Handler {}
-"""),
-        indentedJava("""
-package android.content;
-
-public class ServiceConnection {}
-"""),
-        indentedJava("""
-package android.os;
-
-public enum UserHandle {
-    ALL
-}
-"""),
-        indentedJava(
-            """
-package android.content;
-import android.os.UserHandle;
-import android.os.Handler;
-import android.os.Looper;
-import java.util.concurrent.Executor;
-
-public class Context {
-    public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {}
-    public void registerReceiverAsUser(
-            BroadcastReceiver receiver, UserHandle user, IntentFilter filter,
-            String broadcastPermission, Handler scheduler) {}
-    public void registerReceiverForAllUsers(
-            BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission,
-            Handler scheduler) {}
-    public void sendBroadcast(Intent intent) {}
-    public void sendBroadcast(Intent intent, String receiverPermission) {}
-    public void sendBroadcastAsUser(Intent intent, UserHandle userHandle, String permission) {}
-    public void bindService(Intent intent) {}
-    public void bindServiceAsUser(
-            Intent intent, ServiceConnection connection, int flags, UserHandle userHandle) {}
-    public void unbindService(ServiceConnection connection) {}
-    public Looper getMainLooper() { return null; }
-    public Executor getMainExecutor() { return null; }
-    public Handler getMainThreadHandler() { return null; }
-    public final @Nullable <T> T getSystemService(@NonNull Class<T> serviceClass) { return null; }
-    public abstract @Nullable Object getSystemService(@ServiceName @NonNull String name);
-}
-"""
-        ),
-        indentedJava(
-            """
-package android.app;
-import android.content.Context;
-
-public class Activity extends Context {}
-"""
-        ),
-        indentedJava(
-            """
-package android.graphics;
-
-public class Bitmap {
-    public enum Config {
-        ARGB_8888,
-        RGB_565,
-        HARDWARE
-    }
-    public static Bitmap createBitmap(int width, int height, Config config) {
-        return null;
-    }
-}
-"""
-        ),
-        indentedJava("""
-package android.content;
-
-public class BroadcastReceiver {}
-"""),
-        indentedJava("""
-package android.content;
-
-public class IntentFilter {}
-"""),
+        LibraryReferenceTestFile(File("framework.jar").canonicalFile),
+        LibraryReferenceTestFile(File("androidx.annotation_annotation.jar").canonicalFile),
         indentedJava(
             """
 package com.android.systemui.settings;
@@ -167,23 +45,4 @@
 }
 """
         ),
-        indentedJava(
-            """
-package androidx.annotation;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-import static java.lang.annotation.ElementType.CONSTRUCTOR;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.ElementType.TYPE;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-@Retention(SOURCE)
-@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
-public @interface WorkerThread {
-}
-"""
-        ),
     )
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
index 6ae8fd3..c35ac61 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
@@ -16,18 +16,15 @@
 
 package com.android.internal.systemui.lint
 
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestLintTask
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
 @Suppress("UnstableApiUsage")
-class BindServiceOnMainThreadDetectorTest : LintDetectorTest() {
+class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() {
 
     override fun getDetector(): Detector = BindServiceOnMainThreadDetector()
-    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
 
     override fun getIssues(): List<Issue> = listOf(BindServiceOnMainThreadDetector.ISSUE)
 
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
index 7d42280..376acb5 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
@@ -16,18 +16,15 @@
 
 package com.android.internal.systemui.lint
 
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestLintTask
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
 @Suppress("UnstableApiUsage")
-class BroadcastSentViaContextDetectorTest : LintDetectorTest() {
+class BroadcastSentViaContextDetectorTest : SystemUILintDetectorTest() {
 
     override fun getDetector(): Detector = BroadcastSentViaContextDetector()
-    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
 
     override fun getIssues(): List<Issue> = listOf(BroadcastSentViaContextDetector.ISSUE)
 
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
index c468af8..301c338 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
@@ -16,18 +16,15 @@
 
 package com.android.internal.systemui.lint
 
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestLintTask
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
 @Suppress("UnstableApiUsage")
-class NonInjectedMainThreadDetectorTest : LintDetectorTest() {
+class NonInjectedMainThreadDetectorTest : SystemUILintDetectorTest() {
 
     override fun getDetector(): Detector = NonInjectedMainThreadDetector()
-    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
 
     override fun getIssues(): List<Issue> = listOf(NonInjectedMainThreadDetector.ISSUE)
 
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
index c83a35b..0a74bfc 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
@@ -16,18 +16,15 @@
 
 package com.android.internal.systemui.lint
 
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestLintTask
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
 @Suppress("UnstableApiUsage")
-class NonInjectedServiceDetectorTest : LintDetectorTest() {
+class NonInjectedServiceDetectorTest : SystemUILintDetectorTest() {
 
     override fun getDetector(): Detector = NonInjectedServiceDetector()
-    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
     override fun getIssues(): List<Issue> = listOf(NonInjectedServiceDetector.ISSUE)
 
     @Test
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
index ebcddeb..9ed7aa0 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
@@ -16,18 +16,15 @@
 
 package com.android.internal.systemui.lint
 
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestLintTask
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
 @Suppress("UnstableApiUsage")
-class RegisterReceiverViaContextDetectorTest : LintDetectorTest() {
+class RegisterReceiverViaContextDetectorTest : SystemUILintDetectorTest() {
 
     override fun getDetector(): Detector = RegisterReceiverViaContextDetector()
-    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
 
     override fun getIssues(): List<Issue> = listOf(RegisterReceiverViaContextDetector.ISSUE)
 
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
index b03a11c..54cac7b 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
@@ -16,18 +16,15 @@
 
 package com.android.internal.systemui.lint
 
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestLintTask
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
 @Suppress("UnstableApiUsage")
-class SlowUserQueryDetectorTest : LintDetectorTest() {
+class SlowUserQueryDetectorTest : SystemUILintDetectorTest() {
 
     override fun getDetector(): Detector = SlowUserQueryDetector()
-    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
 
     override fun getIssues(): List<Issue> =
         listOf(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
index fb6537e..c632636 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
@@ -16,18 +16,15 @@
 
 package com.android.internal.systemui.lint
 
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestLintTask
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
 @Suppress("UnstableApiUsage")
-class SoftwareBitmapDetectorTest : LintDetectorTest() {
+class SoftwareBitmapDetectorTest : SystemUILintDetectorTest() {
 
     override fun getDetector(): Detector = SoftwareBitmapDetector()
-    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
 
     override fun getIssues(): List<Issue> = listOf(SoftwareBitmapDetector.ISSUE)
 
@@ -54,12 +51,12 @@
             .run()
             .expect(
                 """
-                src/android/graphics/Bitmap.java:5: Warning: Replace software bitmap with Config.HARDWARE [SoftwareBitmap]
-                        ARGB_8888,
-                        ~~~~~~~~~
-                src/android/graphics/Bitmap.java:6: Warning: Replace software bitmap with Config.HARDWARE [SoftwareBitmap]
-                        RGB_565,
-                        ~~~~~~~
+                src/TestClass.java:5: Warning: Replace software bitmap with Config.HARDWARE [SoftwareBitmap]
+                      Bitmap.createBitmap(300, 300, Bitmap.Config.RGB_565);
+                                                                  ~~~~~~~
+                src/TestClass.java:6: Warning: Replace software bitmap with Config.HARDWARE [SoftwareBitmap]
+                      Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
+                                                                  ~~~~~~~~~
                 0 errors, 2 warnings
                 """
             )
@@ -70,7 +67,7 @@
         lint()
             .files(
                 TestFiles.java(
-                        """
+                    """
                     import android.graphics.Bitmap;
 
                     public class TestClass {
@@ -79,8 +76,7 @@
                         }
                     }
                 """
-                    )
-                    .indented(),
+                ),
                 *stubs
             )
             .issues(SoftwareBitmapDetector.ISSUE)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
new file mode 100644
index 0000000..b83ed70
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
@@ -0,0 +1,208 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+@Suppress("UnstableApiUsage")
+class StaticSettingsProviderDetectorTest : SystemUILintDetectorTest() {
+
+    override fun getDetector(): Detector = StaticSettingsProviderDetector()
+    override fun getIssues(): List<Issue> = listOf(StaticSettingsProviderDetector.ISSUE)
+
+    @Test
+    fun testGetServiceWithString() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                        package test.pkg;
+
+                        import android.provider.Settings;
+                        import android.provider.Settings.Global;
+                        import android.provider.Settings.Secure;
+
+                        public class TestClass {
+                            public void getSystemServiceWithoutDagger(Context context) {
+                                final ContentResolver cr = mContext.getContentResolver();
+                                Global.getFloat(cr, Settings.Global.UNLOCK_SOUND);
+                                Global.getInt(cr, Settings.Global.UNLOCK_SOUND);
+                                Global.getLong(cr, Settings.Global.UNLOCK_SOUND);
+                                Global.getString(cr, Settings.Global.UNLOCK_SOUND);
+                                Global.getFloat(cr, Settings.Global.UNLOCK_SOUND, 1f);
+                                Global.getInt(cr, Settings.Global.UNLOCK_SOUND, 1);
+                                Global.getLong(cr, Settings.Global.UNLOCK_SOUND, 1L);
+                                Global.getString(cr, Settings.Global.UNLOCK_SOUND, "1");
+                                Global.putFloat(cr, Settings.Global.UNLOCK_SOUND, 1f);
+                                Global.putInt(cr, Settings.Global.UNLOCK_SOUND, 1);
+                                Global.putLong(cr, Settings.Global.UNLOCK_SOUND, 1L);
+                                Global.putString(cr, Settings.Global.UNLOCK_SOUND, "1");
+
+                                Secure.getFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+                                Secure.getInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+                                Secure.getLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+                                Secure.getString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+                                Secure.getFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1f);
+                                Secure.getInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1);
+                                Secure.getLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1L);
+                                Secure.getString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, "1");
+                                Secure.putFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1f);
+                                Secure.putInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1);
+                                Secure.putLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1L);
+                                Secure.putString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, "1");
+
+                                Settings.System.getFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+                                Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+                                Settings.System.getLong(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+                                Settings.System.getString(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+                                Settings.System.getFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1f);
+                                Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1);
+                                Settings.System.getLong(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1L);
+                                Settings.System.getString(cr, Settings.System.SCREEN_OFF_TIMEOUT, "1");
+                                Settings.System.putFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1f);
+                                Settings.System.putInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1);
+                                Settings.System.putLong(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1L);
+                                Settings.System.putString(cr, Settings.Global.UNLOCK_SOUND, "1");
+                            }
+                        }
+                        """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(StaticSettingsProviderDetector.ISSUE)
+            .run()
+            .expect(
+                """
+                src/test/pkg/TestClass.java:10: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+                        Global.getFloat(cr, Settings.Global.UNLOCK_SOUND);
+                               ~~~~~~~~
+                src/test/pkg/TestClass.java:11: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+                        Global.getInt(cr, Settings.Global.UNLOCK_SOUND);
+                               ~~~~~~
+                src/test/pkg/TestClass.java:12: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+                        Global.getLong(cr, Settings.Global.UNLOCK_SOUND);
+                               ~~~~~~~
+                src/test/pkg/TestClass.java:13: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+                        Global.getString(cr, Settings.Global.UNLOCK_SOUND);
+                               ~~~~~~~~~
+                src/test/pkg/TestClass.java:14: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+                        Global.getFloat(cr, Settings.Global.UNLOCK_SOUND, 1f);
+                               ~~~~~~~~
+                src/test/pkg/TestClass.java:15: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+                        Global.getInt(cr, Settings.Global.UNLOCK_SOUND, 1);
+                               ~~~~~~
+                src/test/pkg/TestClass.java:16: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+                        Global.getLong(cr, Settings.Global.UNLOCK_SOUND, 1L);
+                               ~~~~~~~
+                src/test/pkg/TestClass.java:17: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+                        Global.getString(cr, Settings.Global.UNLOCK_SOUND, "1");
+                               ~~~~~~~~~
+                src/test/pkg/TestClass.java:18: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+                        Global.putFloat(cr, Settings.Global.UNLOCK_SOUND, 1f);
+                               ~~~~~~~~
+                src/test/pkg/TestClass.java:19: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+                        Global.putInt(cr, Settings.Global.UNLOCK_SOUND, 1);
+                               ~~~~~~
+                src/test/pkg/TestClass.java:20: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+                        Global.putLong(cr, Settings.Global.UNLOCK_SOUND, 1L);
+                               ~~~~~~~
+                src/test/pkg/TestClass.java:21: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+                        Global.putString(cr, Settings.Global.UNLOCK_SOUND, "1");
+                               ~~~~~~~~~
+                src/test/pkg/TestClass.java:23: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+                        Secure.getFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+                               ~~~~~~~~
+                src/test/pkg/TestClass.java:24: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+                        Secure.getInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+                               ~~~~~~
+                src/test/pkg/TestClass.java:25: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+                        Secure.getLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+                               ~~~~~~~
+                src/test/pkg/TestClass.java:26: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+                        Secure.getString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+                               ~~~~~~~~~
+                src/test/pkg/TestClass.java:27: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+                        Secure.getFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1f);
+                               ~~~~~~~~
+                src/test/pkg/TestClass.java:28: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+                        Secure.getInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1);
+                               ~~~~~~
+                src/test/pkg/TestClass.java:29: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+                        Secure.getLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1L);
+                               ~~~~~~~
+                src/test/pkg/TestClass.java:30: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+                        Secure.getString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, "1");
+                               ~~~~~~~~~
+                src/test/pkg/TestClass.java:31: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+                        Secure.putFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1f);
+                               ~~~~~~~~
+                src/test/pkg/TestClass.java:32: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+                        Secure.putInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1);
+                               ~~~~~~
+                src/test/pkg/TestClass.java:33: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+                        Secure.putLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1L);
+                               ~~~~~~~
+                src/test/pkg/TestClass.java:34: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+                        Secure.putString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, "1");
+                               ~~~~~~~~~
+                src/test/pkg/TestClass.java:36: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+                        Settings.System.getFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+                                        ~~~~~~~~
+                src/test/pkg/TestClass.java:37: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+                        Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+                                        ~~~~~~
+                src/test/pkg/TestClass.java:38: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+                        Settings.System.getLong(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+                                        ~~~~~~~
+                src/test/pkg/TestClass.java:39: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+                        Settings.System.getString(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+                                        ~~~~~~~~~
+                src/test/pkg/TestClass.java:40: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+                        Settings.System.getFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1f);
+                                        ~~~~~~~~
+                src/test/pkg/TestClass.java:41: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+                        Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1);
+                                        ~~~~~~
+                src/test/pkg/TestClass.java:42: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+                        Settings.System.getLong(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1L);
+                                        ~~~~~~~
+                src/test/pkg/TestClass.java:43: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+                        Settings.System.getString(cr, Settings.System.SCREEN_OFF_TIMEOUT, "1");
+                                        ~~~~~~~~~
+                src/test/pkg/TestClass.java:44: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+                        Settings.System.putFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1f);
+                                        ~~~~~~~~
+                src/test/pkg/TestClass.java:45: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+                        Settings.System.putInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1);
+                                        ~~~~~~
+                src/test/pkg/TestClass.java:46: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+                        Settings.System.putLong(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1L);
+                                        ~~~~~~~
+                src/test/pkg/TestClass.java:47: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+                        Settings.System.putString(cr, Settings.Global.UNLOCK_SOUND, "1");
+                                        ~~~~~~~~~
+                0 errors, 36 warnings
+                """
+            )
+    }
+
+    private val stubs = androidStubs
+}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
new file mode 100644
index 0000000..3f93f07
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
@@ -0,0 +1,48 @@
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import java.io.File
+import org.junit.ClassRule
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.runners.model.Statement
+
+@Suppress("UnstableApiUsage")
+@RunWith(JUnit4::class)
+abstract class SystemUILintDetectorTest : LintDetectorTest() {
+
+    companion object {
+        @ClassRule
+        @JvmField
+        val libraryChecker: LibraryExists =
+            LibraryExists("framework.jar", "androidx.annotation_annotation.jar")
+    }
+
+    class LibraryExists(vararg val libraryNames: String) : TestRule {
+        override fun apply(base: Statement, description: Description): Statement {
+            return object : Statement() {
+                override fun evaluate() {
+                    for (libName in libraryNames) {
+                        val libFile = File(libName)
+                        if (!libFile.canonicalFile.exists()) {
+                            throw Exception(
+                                "Could not find $libName in the test's working directory. " +
+                                    "File ${libFile.absolutePath} does not exist."
+                            )
+                        }
+                    }
+                    base.evaluate()
+                }
+            }
+        }
+    }
+    /**
+     * Customize the lint task to disable SDK usage completely. This ensures that running the tests
+     * in Android Studio has the same result as running the tests in atest
+     */
+    override fun lint(): TestLintTask =
+        super.lint().allowMissingSdk(true).sdkHome(File("/dev/null"))
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
index 065c314..50c3d7e 100644
--- a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
@@ -40,17 +40,16 @@
 import androidx.compose.ui.unit.LayoutDirection
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.animation.Expandable
 import com.android.systemui.animation.LaunchAnimator
 import kotlin.math.roundToInt
 
-/** A controller that can control animated launches. */
+/** A controller that can control animated launches from an [Expandable]. */
 interface ExpandableController {
-    /** Create an [ActivityLaunchAnimator.Controller] to animate into an Activity. */
-    fun forActivity(): ActivityLaunchAnimator.Controller
-
-    /** Create a [DialogLaunchAnimator.Controller] to animate into a Dialog. */
-    fun forDialog(): DialogLaunchAnimator.Controller
+    /** The [Expandable] controlled by this controller. */
+    val expandable: Expandable
 }
 
 /**
@@ -120,13 +119,26 @@
     private val layoutDirection: LayoutDirection,
     private val isComposed: State<Boolean>,
 ) : ExpandableController {
-    override fun forActivity(): ActivityLaunchAnimator.Controller {
-        return activityController()
-    }
+    override val expandable: Expandable =
+        object : Expandable {
+            override fun activityLaunchController(
+                cujType: Int?,
+            ): ActivityLaunchAnimator.Controller? {
+                if (!isComposed.value) {
+                    return null
+                }
 
-    override fun forDialog(): DialogLaunchAnimator.Controller {
-        return dialogController()
-    }
+                return activityController(cujType)
+            }
+
+            override fun dialogLaunchController(cuj: DialogCuj?): DialogLaunchAnimator.Controller? {
+                if (!isComposed.value) {
+                    return null
+                }
+
+                return dialogController(cuj)
+            }
+        }
 
     /**
      * Create a [LaunchAnimator.Controller] that is going to be used to drive an activity or dialog
@@ -233,7 +245,7 @@
     }
 
     /** Create an [ActivityLaunchAnimator.Controller] that can be used to animate activities. */
-    private fun activityController(): ActivityLaunchAnimator.Controller {
+    private fun activityController(cujType: Int?): ActivityLaunchAnimator.Controller {
         val delegate = launchController()
         return object : ActivityLaunchAnimator.Controller, LaunchAnimator.Controller by delegate {
             override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
@@ -248,10 +260,11 @@
         }
     }
 
-    private fun dialogController(): DialogLaunchAnimator.Controller {
+    private fun dialogController(cuj: DialogCuj?): DialogLaunchAnimator.Controller {
         return object : DialogLaunchAnimator.Controller {
             override val viewRoot: ViewRootImpl = composeViewRoot.viewRootImpl
             override val sourceIdentity: Any = this@ExpandableControllerImpl
+            override val cuj: DialogCuj? = cuj
 
             override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
                 val newOverlay = viewGroup.overlay as ViewGroupOverlay
@@ -294,9 +307,7 @@
                 isDialogShowing.value = false
             }
 
-            override fun jankConfigurationBuilder(
-                cuj: Int
-            ): InteractionJankMonitor.Configuration.Builder? {
+            override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? {
                 // TODO(b/252723237): Add support for jank monitoring when animating from a
                 // Composable.
                 return null
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 491ec20..da612a9 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -189,45 +189,8 @@
 -packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt
 -packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
 -packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
--packages/SystemUI/src/com/android/systemui/media/AnimationBindHandler.kt
--packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt
--packages/SystemUI/src/com/android/systemui/media/GutsViewHolder.kt
--packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt
--packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
--packages/SystemUI/src/com/android/systemui/media/LightSourceDrawable.kt
--packages/SystemUI/src/com/android/systemui/media/LocalMediaManagerFactory.kt
--packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
--packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt
--packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
--packages/SystemUI/src/com/android/systemui/media/MediaData.kt
--packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
--packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
--packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
--packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
--packages/SystemUI/src/com/android/systemui/media/MediaFeatureFlag.kt
--packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
--packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
--packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
--packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt
 -packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
 -packages/SystemUI/src/com/android/systemui/media/MediaProjectionCaptureTarget.kt
--packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
--packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt
--packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
--packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
--packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt
--packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
--packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
--packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
--packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt
--packages/SystemUI/src/com/android/systemui/media/MetadataAnimationHandler.kt
--packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt
--packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt
--packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
--packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
--packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
--packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt
--packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt
 -packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
 -packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
 -packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -246,8 +209,6 @@
 -packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
 -packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
 -packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt
 -packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
 -packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLogger.kt
 -packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
@@ -528,6 +489,8 @@
 -packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
 -packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
 -packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+-packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+-packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarRootView.kt
 -packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt
 -packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
 -packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt
@@ -653,32 +616,11 @@
 -packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt
 -packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/AnimationBindHandlerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MetadataAnimationHandlerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/SmartspaceMediaDataTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/SquigglyProgressTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
index b3dd955..dee0f5c 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
@@ -205,6 +205,13 @@
             n1 = TonalSpec(HueSource(), ChromaMultiple(0.0833)),
             n2 = TonalSpec(HueSource(), ChromaMultiple(0.1666))
     )),
+    MONOCHROMATIC(CoreSpec(
+            a1 = TonalSpec(HueSource(), ChromaConstant(.0)),
+            a2 = TonalSpec(HueSource(), ChromaConstant(.0)),
+            a3 = TonalSpec(HueSource(), ChromaConstant(.0)),
+            n1 = TonalSpec(HueSource(), ChromaConstant(.0)),
+            n2 = TonalSpec(HueSource(), ChromaConstant(.0))
+    )),
 }
 
 class ColorScheme(
@@ -219,7 +226,7 @@
     val neutral1: List<Int>
     val neutral2: List<Int>
 
-    constructor(@ColorInt seed: Int, darkTheme: Boolean):
+    constructor(@ColorInt seed: Int, darkTheme: Boolean) :
             this(seed, darkTheme, Style.TONAL_SPOT)
 
     @JvmOverloads
@@ -227,7 +234,7 @@
         wallpaperColors: WallpaperColors,
         darkTheme: Boolean,
         style: Style = Style.TONAL_SPOT
-    ):
+    ) :
             this(getSeedColor(wallpaperColors, style != Style.CONTENT), darkTheme, style)
 
     val allAccentColors: List<Int>
@@ -472,4 +479,4 @@
             return huePopulation
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index cafaaf8..7709f21 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -33,6 +33,7 @@
 
     static_libs: [
         "androidx.annotation_annotation",
+        "error_prone_annotations",
         "PluginCoreLib",
         "SystemUIAnimationLib",
     ],
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 1e74c3d..89f5c2c 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -14,9 +14,11 @@
 package com.android.systemui.plugins
 
 import android.content.res.Resources
+import android.graphics.Rect
 import android.graphics.drawable.Drawable
 import android.view.View
 import com.android.systemui.plugins.annotations.ProvidesInterface
+import com.android.systemui.plugins.log.LogBuffer
 import java.io.PrintWriter
 import java.util.Locale
 import java.util.TimeZone
@@ -69,6 +71,9 @@
 
     /** Optional method for dumping debug information */
     fun dump(pw: PrintWriter) { }
+
+    /** Optional method for debug logging */
+    fun setLogBuffer(logBuffer: LogBuffer) { }
 }
 
 /** Interface for a specific clock face version rendered by the clock */
@@ -114,6 +119,17 @@
 
     /** Runs the battery animation (if any). */
     fun charge() { }
+
+    /** Move the clock, for example, if the notification tray appears in split-shade mode. */
+    fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) { }
+
+    /**
+     * Whether this clock has a custom position update animation. If true, the keyguard will call
+     * `onPositionUpdated` to notify the clock of a position update animation. If false, a default
+     * animation will be used (e.g. a simple translation).
+     */
+    val hasCustomPositionUpdatedAnimation
+        get() = false
 }
 
 /** Events that have specific data about the related face */
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
similarity index 85%
rename from packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
rename to packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
index 6124e10..6436dcb 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.log
+package com.android.systemui.plugins.log
 
 import android.os.Trace
 import android.util.Log
-import com.android.systemui.log.dagger.LogModule
-import com.android.systemui.util.collection.RingBuffer
+import com.android.systemui.plugins.util.RingBuffer
 import com.google.errorprone.annotations.CompileTimeConstant
 import java.io.PrintWriter
 import java.util.concurrent.ArrayBlockingQueue
@@ -61,15 +60,18 @@
  * In either case, `level` can be any of `verbose`, `debug`, `info`, `warn`, `error`, `assert`, or
  * the first letter of any of the previous.
  *
- * Buffers are provided by [LogModule]. Instances should be created using a [LogBufferFactory].
+ * In SystemUI, buffers are provided by LogModule. Instances should be created using a SysUI
+ * LogBufferFactory.
  *
  * @param name The name of this buffer, printed when the buffer is dumped and in some other
  * situations.
  * @param maxSize The maximum number of messages to keep in memory at any one time. Buffers start
- * out empty and grow up to [maxSize] as new messages are logged. Once the buffer's size reaches
- * the maximum, it behaves like a ring buffer.
+ * out empty and grow up to [maxSize] as new messages are logged. Once the buffer's size reaches the
+ * maximum, it behaves like a ring buffer.
  */
-class LogBuffer @JvmOverloads constructor(
+class LogBuffer
+@JvmOverloads
+constructor(
     private val name: String,
     private val maxSize: Int,
     private val logcatEchoTracker: LogcatEchoTracker,
@@ -78,7 +80,7 @@
     private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() }
 
     private val echoMessageQueue: BlockingQueue<LogMessage>? =
-            if (logcatEchoTracker.logInBackgroundThread) ArrayBlockingQueue(10) else null
+        if (logcatEchoTracker.logInBackgroundThread) ArrayBlockingQueue(10) else null
 
     init {
         if (logcatEchoTracker.logInBackgroundThread && echoMessageQueue != null) {
@@ -133,11 +135,11 @@
      */
     @JvmOverloads
     inline fun log(
-            tag: String,
-            level: LogLevel,
-            messageInitializer: MessageInitializer,
-            noinline messagePrinter: MessagePrinter,
-            exception: Throwable? = null,
+        tag: String,
+        level: LogLevel,
+        messageInitializer: MessageInitializer,
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
     ) {
         val message = obtain(tag, level, messagePrinter, exception)
         messageInitializer(message)
@@ -152,14 +154,13 @@
      * log message is built during runtime, use the [LogBuffer.log] overloaded method that takes in
      * an initializer and a message printer.
      *
-     * Log buffers are limited by the number of entries, so logging more frequently
-     * will limit the time window that the LogBuffer covers in a bug report.  Richer logs, on the
-     * other hand, make a bug report more actionable, so using the [log] with a messagePrinter to
-     * add more detail to every log may do more to improve overall logging than adding more logs
-     * with this method.
+     * Log buffers are limited by the number of entries, so logging more frequently will limit the
+     * time window that the LogBuffer covers in a bug report. Richer logs, on the other hand, make a
+     * bug report more actionable, so using the [log] with a messagePrinter to add more detail to
+     * every log may do more to improve overall logging than adding more logs with this method.
      */
     fun log(tag: String, level: LogLevel, @CompileTimeConstant message: String) =
-            log(tag, level, {str1 = message}, { str1!! })
+        log(tag, level, { str1 = message }, { str1!! })
 
     /**
      * You should call [log] instead of this method.
@@ -172,10 +173,10 @@
      */
     @Synchronized
     fun obtain(
-            tag: String,
-            level: LogLevel,
-            messagePrinter: MessagePrinter,
-            exception: Throwable? = null,
+        tag: String,
+        level: LogLevel,
+        messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
     ): LogMessage {
         if (!mutable) {
             return FROZEN_MESSAGE
@@ -189,8 +190,7 @@
      * You should call [log] instead of this method.
      *
      * After acquiring a message via [obtain], call this method to signal to the buffer that you
-     * have finished filling in its data fields. The message will be echoed to logcat if
-     * necessary.
+     * have finished filling in its data fields. The message will be echoed to logcat if necessary.
      */
     @Synchronized
     fun commit(message: LogMessage) {
@@ -213,7 +213,8 @@
 
     /** Sends message to echo after determining whether to use Logcat and/or systrace. */
     private fun echoToDesiredEndpoints(message: LogMessage) {
-        val includeInLogcat = logcatEchoTracker.isBufferLoggable(name, message.level) ||
+        val includeInLogcat =
+            logcatEchoTracker.isBufferLoggable(name, message.level) ||
                 logcatEchoTracker.isTagLoggable(message.tag, message.level)
         echo(message, toLogcat = includeInLogcat, toSystrace = systrace)
     }
@@ -221,7 +222,12 @@
     /** Converts the entire buffer to a newline-delimited string */
     @Synchronized
     fun dump(pw: PrintWriter, tailLength: Int) {
-        val iterationStart = if (tailLength <= 0) { 0 } else { max(0, buffer.size - tailLength) }
+        val iterationStart =
+            if (tailLength <= 0) {
+                0
+            } else {
+                max(0, buffer.size - tailLength)
+            }
 
         for (i in iterationStart until buffer.size) {
             buffer[i].dump(pw)
@@ -229,9 +235,9 @@
     }
 
     /**
-     * "Freezes" the contents of the buffer, making it immutable until [unfreeze] is called.
-     * Calls to [log], [obtain], and [commit] will not affect the buffer and will return dummy
-     * values if necessary.
+     * "Freezes" the contents of the buffer, making it immutable until [unfreeze] is called. Calls
+     * to [log], [obtain], and [commit] will not affect the buffer and will return dummy values if
+     * necessary.
      */
     @Synchronized
     fun freeze() {
@@ -241,9 +247,7 @@
         }
     }
 
-    /**
-     * Undoes the effects of calling [freeze].
-     */
+    /** Undoes the effects of calling [freeze]. */
     @Synchronized
     fun unfreeze() {
         if (frozen) {
@@ -265,8 +269,11 @@
     }
 
     private fun echoToSystrace(message: LogMessage, strMessage: String) {
-        Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events",
-            "$name - ${message.level.shortString} ${message.tag}: $strMessage")
+        Trace.instantForTrack(
+            Trace.TRACE_TAG_APP,
+            "UI Events",
+            "$name - ${message.level.shortString} ${message.tag}: $strMessage"
+        )
     }
 
     private fun echoToLogcat(message: LogMessage, strMessage: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogLevel.kt
similarity index 83%
rename from packages/SystemUI/src/com/android/systemui/log/LogLevel.kt
rename to packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogLevel.kt
index 53f231c..b036cf0 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogLevel.kt
@@ -14,17 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.log
+package com.android.systemui.plugins.log
 
 import android.util.Log
 
-/**
- * Enum version of @Log.Level
- */
-enum class LogLevel(
-    @Log.Level val nativeLevel: Int,
-    val shortString: String
-) {
+/** Enum version of @Log.Level */
+enum class LogLevel(@Log.Level val nativeLevel: Int, val shortString: String) {
     VERBOSE(Log.VERBOSE, "V"),
     DEBUG(Log.DEBUG, "D"),
     INFO(Log.INFO, "I"),
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessage.kt
similarity index 75%
rename from packages/SystemUI/src/com/android/systemui/log/LogMessage.kt
rename to packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessage.kt
index dae2592..9468681 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessage.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.log
+package com.android.systemui.plugins.log
 
 import java.io.PrintWriter
 import java.text.SimpleDateFormat
@@ -29,9 +29,10 @@
  *
  * When a message is logged, the code doing the logging stores data in one or more of the generic
  * fields ([str1], [int1], etc). When it comes time to dump the message to logcat/bugreport/etc, the
- * [messagePrinter] function reads the data stored in the generic fields and converts that to a human-
- * readable string. Thus, for every log type there must be a specialized initializer function that
- * stores data specific to that log type and a specialized printer function that prints that data.
+ * [messagePrinter] function reads the data stored in the generic fields and converts that to a
+ * human- readable string. Thus, for every log type there must be a specialized initializer function
+ * that stores data specific to that log type and a specialized printer function that prints that
+ * data.
  *
  * See [LogBuffer.log] for more information.
  */
@@ -55,9 +56,7 @@
     var bool3: Boolean
     var bool4: Boolean
 
-    /**
-     * Function that dumps the [LogMessage] to the provided [writer].
-     */
+    /** Function that dumps the [LogMessage] to the provided [writer]. */
     fun dump(writer: PrintWriter) {
         val formattedTimestamp = DATE_FORMAT.format(timestamp)
         val shortLevel = level.shortString
@@ -68,12 +67,12 @@
 }
 
 /**
- * A function that will be called if and when the message needs to be dumped to
- * logcat or a bug report. It should read the data stored by the initializer and convert it to
- * a human-readable string. The value of `this` will be the LogMessage to be printed.
- * **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and NEVER any
- * variables in its enclosing scope. Otherwise, the runtime will need to allocate a new instance
- * of the printer for each call, thwarting our attempts at avoiding any sort of allocation.
+ * A function that will be called if and when the message needs to be dumped to logcat or a bug
+ * report. It should read the data stored by the initializer and convert it to a human-readable
+ * string. The value of `this` will be the LogMessage to be printed. **IMPORTANT:** The printer
+ * should ONLY ever reference fields on the LogMessage and NEVER any variables in its enclosing
+ * scope. Otherwise, the runtime will need to allocate a new instance of the printer for each call,
+ * thwarting our attempts at avoiding any sort of allocation.
  */
 typealias MessagePrinter = LogMessage.() -> String
 
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessageImpl.kt
similarity index 78%
rename from packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt
rename to packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessageImpl.kt
index 4dd6f65..f2a6a91 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessageImpl.kt
@@ -14,11 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.log
+package com.android.systemui.plugins.log
 
-/**
- * Recyclable implementation of [LogMessage].
- */
+/** Recyclable implementation of [LogMessage]. */
 data class LogMessageImpl(
     override var level: LogLevel,
     override var tag: String,
@@ -68,23 +66,24 @@
     companion object Factory {
         fun create(): LogMessageImpl {
             return LogMessageImpl(
-                    LogLevel.DEBUG,
-                    DEFAULT_TAG,
-                    0,
-                    DEFAULT_PRINTER,
-                    null,
-                    null,
-                    null,
-                    null,
-                    0,
-                    0,
-                    0,
-                    0,
-                    0.0,
-                    false,
-                    false,
-                    false,
-                    false)
+                LogLevel.DEBUG,
+                DEFAULT_TAG,
+                0,
+                DEFAULT_PRINTER,
+                null,
+                null,
+                null,
+                null,
+                0,
+                0,
+                0,
+                0,
+                0.0,
+                false,
+                false,
+                false,
+                false
+            )
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTracker.kt
similarity index 68%
rename from packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt
rename to packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTracker.kt
index 8cda423..cfe894f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTracker.kt
@@ -14,24 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.log
+package com.android.systemui.plugins.log
 
-/**
- * Keeps track of which [LogBuffer] messages should also appear in logcat.
- */
+/** Keeps track of which [LogBuffer] messages should also appear in logcat. */
 interface LogcatEchoTracker {
-    /**
-     * Whether [bufferName] should echo messages of [level] or higher to logcat.
-     */
+    /** Whether [bufferName] should echo messages of [level] or higher to logcat. */
     fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean
 
-    /**
-     * Whether [tagName] should echo messages of [level] or higher to logcat.
-     */
+    /** Whether [tagName] should echo messages of [level] or higher to logcat. */
     fun isTagLoggable(tagName: String, level: LogLevel): Boolean
 
-    /**
-     * Whether to log messages in a background thread.
-     */
+    /** Whether to log messages in a background thread. */
     val logInBackgroundThread: Boolean
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt
similarity index 73%
rename from packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
rename to packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt
index 40b0cdc..d3fabac 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.log
+package com.android.systemui.plugins.log
 
 import android.content.ContentResolver
 import android.database.ContentObserver
@@ -36,19 +36,15 @@
  * $ adb shell settings put global systemui/tag/<tag> <level>
  * ```
  */
-class LogcatEchoTrackerDebug private constructor(
-    private val contentResolver: ContentResolver
-) : LogcatEchoTracker {
+class LogcatEchoTrackerDebug private constructor(private val contentResolver: ContentResolver) :
+    LogcatEchoTracker {
     private val cachedBufferLevels: MutableMap<String, LogLevel> = mutableMapOf()
     private val cachedTagLevels: MutableMap<String, LogLevel> = mutableMapOf()
     override val logInBackgroundThread = true
 
     companion object Factory {
         @JvmStatic
-        fun create(
-            contentResolver: ContentResolver,
-            mainLooper: Looper
-        ): LogcatEchoTrackerDebug {
+        fun create(contentResolver: ContentResolver, mainLooper: Looper): LogcatEchoTrackerDebug {
             val tracker = LogcatEchoTrackerDebug(contentResolver)
             tracker.attach(mainLooper)
             return tracker
@@ -57,37 +53,35 @@
 
     private fun attach(mainLooper: Looper) {
         contentResolver.registerContentObserver(
-                Settings.Global.getUriFor(BUFFER_PATH),
-                true,
-                object : ContentObserver(Handler(mainLooper)) {
-                    override fun onChange(selfChange: Boolean, uri: Uri?) {
-                        super.onChange(selfChange, uri)
-                        cachedBufferLevels.clear()
-                    }
-                })
+            Settings.Global.getUriFor(BUFFER_PATH),
+            true,
+            object : ContentObserver(Handler(mainLooper)) {
+                override fun onChange(selfChange: Boolean, uri: Uri?) {
+                    super.onChange(selfChange, uri)
+                    cachedBufferLevels.clear()
+                }
+            }
+        )
 
         contentResolver.registerContentObserver(
-                Settings.Global.getUriFor(TAG_PATH),
-                true,
-                object : ContentObserver(Handler(mainLooper)) {
-                    override fun onChange(selfChange: Boolean, uri: Uri?) {
-                        super.onChange(selfChange, uri)
-                        cachedTagLevels.clear()
-                    }
-                })
+            Settings.Global.getUriFor(TAG_PATH),
+            true,
+            object : ContentObserver(Handler(mainLooper)) {
+                override fun onChange(selfChange: Boolean, uri: Uri?) {
+                    super.onChange(selfChange, uri)
+                    cachedTagLevels.clear()
+                }
+            }
+        )
     }
 
-    /**
-     * Whether [bufferName] should echo messages of [level] or higher to logcat.
-     */
+    /** Whether [bufferName] should echo messages of [level] or higher to logcat. */
     @Synchronized
     override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean {
         return level.ordinal >= getLogLevel(bufferName, BUFFER_PATH, cachedBufferLevels).ordinal
     }
 
-    /**
-     * Whether [tagName] should echo messages of [level] or higher to logcat.
-     */
+    /** Whether [tagName] should echo messages of [level] or higher to logcat. */
     @Synchronized
     override fun isTagLoggable(tagName: String, level: LogLevel): Boolean {
         return level >= getLogLevel(tagName, TAG_PATH, cachedTagLevels)
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerProd.kt
similarity index 89%
rename from packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
rename to packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerProd.kt
index 1a4ad19..3c8bda4 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerProd.kt
@@ -14,11 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.log
+package com.android.systemui.plugins.log
 
-/**
- * Production version of [LogcatEchoTracker] that isn't configurable.
- */
+/** Production version of [LogcatEchoTracker] that isn't configurable. */
 class LogcatEchoTrackerProd : LogcatEchoTracker {
     override val logInBackgroundThread = false
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/collection/RingBuffer.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt
similarity index 82%
rename from packages/SystemUI/src/com/android/systemui/util/collection/RingBuffer.kt
rename to packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt
index 97dc842..68d7890 100644
--- a/packages/SystemUI/src/com/android/systemui/util/collection/RingBuffer.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.collection
+package com.android.systemui.plugins.util
 
 import kotlin.math.max
 
@@ -32,19 +32,16 @@
  * @param factory A function that creates a fresh instance of T. Used by the buffer while it's
  * growing to [maxSize].
  */
-class RingBuffer<T>(
-    private val maxSize: Int,
-    private val factory: () -> T
-) : Iterable<T> {
+class RingBuffer<T>(private val maxSize: Int, private val factory: () -> T) : Iterable<T> {
 
     private val buffer = MutableList<T?>(maxSize) { null }
 
     /**
      * An abstract representation that points to the "end" of the buffer. Increments every time
-     * [advance] is called and never wraps. Use [indexOf] to calculate the associated index into
-     * the backing array. Always points to the "next" available slot in the buffer. Before the
-     * buffer has completely filled, the value pointed to will be null. Afterward, it will be the
-     * value at the "beginning" of the buffer.
+     * [advance] is called and never wraps. Use [indexOf] to calculate the associated index into the
+     * backing array. Always points to the "next" available slot in the buffer. Before the buffer
+     * has completely filled, the value pointed to will be null. Afterward, it will be the value at
+     * the "beginning" of the buffer.
      *
      * This value is unlikely to overflow. Assuming [advance] is called at rate of 100 calls/ms,
      * omega will overflow after a little under three million years of continuous operation.
@@ -60,24 +57,23 @@
 
     /**
      * Advances the buffer's position by one and returns the value that is now present at the "end"
-     * of the buffer. If the buffer is not yet full, uses [factory] to create a new item.
-     * Otherwise, reuses the value that was previously at the "beginning" of the buffer.
+     * of the buffer. If the buffer is not yet full, uses [factory] to create a new item. Otherwise,
+     * reuses the value that was previously at the "beginning" of the buffer.
      *
-     * IMPORTANT: The value is returned as-is, without being reset. It will retain any data that
-     * was previously stored on it.
+     * IMPORTANT: The value is returned as-is, without being reset. It will retain any data that was
+     * previously stored on it.
      */
     fun advance(): T {
         val index = indexOf(omega)
         omega += 1
-        val entry = buffer[index] ?: factory().also {
-            buffer[index] = it
-        }
+        val entry = buffer[index] ?: factory().also { buffer[index] = it }
         return entry
     }
 
     /**
      * Returns the value stored at [index], which can range from 0 (the "start", or oldest element
-     * of the buffer) to [size] - 1 (the "end", or newest element of the buffer).
+     * of the buffer) to [size]
+     * - 1 (the "end", or newest element of the buffer).
      */
     operator fun get(index: Int): T {
         if (index < 0 || index >= size) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt b/packages/SystemUI/plugin/tests/log/LogBufferTest.kt
similarity index 74%
rename from packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
rename to packages/SystemUI/plugin/tests/log/LogBufferTest.kt
index 56aff3c..a39b856 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
+++ b/packages/SystemUI/plugin/tests/log/LogBufferTest.kt
@@ -2,6 +2,7 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.log.LogBuffer
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
@@ -18,8 +19,7 @@
 
     private lateinit var outputWriter: StringWriter
 
-    @Mock
-    private lateinit var logcatEchoTracker: LogcatEchoTracker
+    @Mock private lateinit var logcatEchoTracker: LogcatEchoTracker
 
     @Before
     fun setup() {
@@ -67,15 +67,17 @@
     @Test
     fun dump_writesCauseAndStacktrace() {
         buffer = createBuffer()
-        val exception = createTestException("Exception message",
+        val exception =
+            createTestException(
+                "Exception message",
                 "TestClass",
-                cause = createTestException("The real cause!", "TestClass"))
+                cause = createTestException("The real cause!", "TestClass")
+            )
         buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
 
         val dumpedString = dumpBuffer()
 
-        assertThat(dumpedString)
-                .contains("Caused by: java.lang.RuntimeException: The real cause!")
+        assertThat(dumpedString).contains("Caused by: java.lang.RuntimeException: The real cause!")
         assertThat(dumpedString).contains("at TestClass.TestMethod(TestClass.java:1)")
         assertThat(dumpedString).contains("at TestClass.TestMethod(TestClass.java:2)")
     }
@@ -85,49 +87,47 @@
         buffer = createBuffer()
         val exception = RuntimeException("Root exception message")
         exception.addSuppressed(
-                createTestException(
-                        "First suppressed exception",
-                        "FirstClass",
-                        createTestException("Cause of suppressed exp", "ThirdClass")
-                ))
-        exception.addSuppressed(
-                createTestException("Second suppressed exception", "SecondClass"))
+            createTestException(
+                "First suppressed exception",
+                "FirstClass",
+                createTestException("Cause of suppressed exp", "ThirdClass")
+            )
+        )
+        exception.addSuppressed(createTestException("Second suppressed exception", "SecondClass"))
         buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
 
         val dumpedStr = dumpBuffer()
 
         // first suppressed exception
         assertThat(dumpedStr)
-                .contains("Suppressed: " +
-                        "java.lang.RuntimeException: First suppressed exception")
+            .contains("Suppressed: " + "java.lang.RuntimeException: First suppressed exception")
         assertThat(dumpedStr).contains("at FirstClass.TestMethod(FirstClass.java:1)")
         assertThat(dumpedStr).contains("at FirstClass.TestMethod(FirstClass.java:2)")
 
         assertThat(dumpedStr)
-                .contains("Caused by: java.lang.RuntimeException: Cause of suppressed exp")
+            .contains("Caused by: java.lang.RuntimeException: Cause of suppressed exp")
         assertThat(dumpedStr).contains("at ThirdClass.TestMethod(ThirdClass.java:1)")
         assertThat(dumpedStr).contains("at ThirdClass.TestMethod(ThirdClass.java:2)")
 
         // second suppressed exception
         assertThat(dumpedStr)
-                .contains("Suppressed: " +
-                        "java.lang.RuntimeException: Second suppressed exception")
+            .contains("Suppressed: " + "java.lang.RuntimeException: Second suppressed exception")
         assertThat(dumpedStr).contains("at SecondClass.TestMethod(SecondClass.java:1)")
         assertThat(dumpedStr).contains("at SecondClass.TestMethod(SecondClass.java:2)")
     }
 
     private fun createTestException(
-            message: String,
-            errorClass: String,
-            cause: Throwable? = null,
+        message: String,
+        errorClass: String,
+        cause: Throwable? = null,
     ): Exception {
         val exception = RuntimeException(message, cause)
-        exception.stackTrace = (1..5).map { lineNumber ->
-            StackTraceElement(errorClass,
-                    "TestMethod",
-                    "$errorClass.java",
-                    lineNumber)
-        }.toTypedArray()
+        exception.stackTrace =
+            (1..5)
+                .map { lineNumber ->
+                    StackTraceElement(errorClass, "TestMethod", "$errorClass.java", lineNumber)
+                }
+                .toTypedArray()
         return exception
     }
 
diff --git a/packages/SystemUI/res-keyguard/drawable/media_squiggly_progress.xml b/packages/SystemUI/res-keyguard/drawable/fullscreen_userswitcher_menu_item_divider.xml
similarity index 71%
copy from packages/SystemUI/res-keyguard/drawable/media_squiggly_progress.xml
copy to packages/SystemUI/res-keyguard/drawable/fullscreen_userswitcher_menu_item_divider.xml
index 9e61236..de0e526 100644
--- a/packages/SystemUI/res-keyguard/drawable/media_squiggly_progress.xml
+++ b/packages/SystemUI/res-keyguard/drawable/fullscreen_userswitcher_menu_item_divider.xml
@@ -12,6 +12,9 @@
   ~ 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
   -->
-<com.android.systemui.media.SquigglyProgress />
\ No newline at end of file
+<shape xmlns:android="http://schemas.android.com/apk/res/android" >
+    <size android:height="@dimen/bouncer_user_switcher_popup_items_divider_height"/>
+    <solid android:color="@color/user_switcher_fullscreen_bg"/>
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 3ad7c8c..d64587d 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -37,6 +37,7 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_marginTop="@dimen/keyguard_large_clock_top_margin"
+        android:clipChildren="false"
         android:visibility="gone" />
 
     <!-- Not quite optimal but needed to translate these items as a group. The
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
index 16a1d94..647abee 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
@@ -27,6 +27,7 @@
     systemui:layout_constraintEnd_toEndOf="parent"
     systemui:layout_constraintTop_toTopOf="parent"
     android:layout_marginHorizontal="@dimen/status_view_margin_horizontal"
+    android:clipChildren="false"
     android:layout_width="0dp"
     android:layout_height="wrap_content">
     <LinearLayout
diff --git a/packages/SystemUI/res-keyguard/values-af/strings.xml b/packages/SystemUI/res-keyguard/values-af/strings.xml
index d5e84f9..d5552f6 100644
--- a/packages/SystemUI/res-keyguard/values-af/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-af/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Patroon word vereis nadat toestel herbegin het"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN word vereis nadat toestel herbegin het"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Wagwoord word vereis nadat toestel herbegin het"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Patroon word vir bykomende sekuriteit vereis"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN word vir bykomende sekuriteit vereis"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Wagwoord word vir bykomende sekuriteit vereis"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Toestel is deur administrateur gesluit"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Toestel is handmatig gesluit"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nie herken nie"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Verstek"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Borrel"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analoog"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-am/strings.xml b/packages/SystemUI/res-keyguard/values-am/strings.xml
index be52c44..533e5a2 100644
--- a/packages/SystemUI/res-keyguard/values-am/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-am/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"መሣሪያ ዳግም ከጀመረ በኋላ ሥርዓተ ጥለት ያስፈልጋል"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"መሣሪያ ዳግም ከተነሳ በኋላ ፒን ያስፈልጋል"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"መሣሪያ ዳግም ከጀመረ በኋላ የይለፍ ቃል ያስፈልጋል"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ሥርዓተ ጥለት ለተጨማሪ ደህንነት ያስፈልጋል"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ፒን ለተጨማሪ ደህንነት ያስፈልጋል"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"የይለፍ ቃል ለተጨማሪ ደህንነት ያስፈልጋል"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"መሣሪያ በአስተዳዳሪ ተቆልፏል"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"መሣሪያ በተጠቃሚው ራሱ ተቆልፏል"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"አልታወቀም"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ነባሪ"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"አረፋ"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"አናሎግ"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml
index adb57b6..81ce7d3 100644
--- a/packages/SystemUI/res-keyguard/values-ar/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"يجب رسم النقش بعد إعادة تشغيل الجهاز"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"يجب إدخال رقم التعريف الشخصي بعد إعادة تشغيل الجهاز"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"يجب إدخال كلمة المرور بعد إعادة تشغيل الجهاز"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"يجب رسم النقش لمزيد من الأمان"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"يجب إدخال رقم التعريف الشخصي لمزيد من الأمان"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"يجب إدخال كلمة المرور لمزيد من الأمان"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"اختار المشرف قفل الجهاز"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"تم حظر الجهاز يدويًا"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"لم يتم التعرّف عليه."</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"تلقائي"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"فقاعة"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"ساعة تقليدية"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-as/strings.xml b/packages/SystemUI/res-keyguard/values-as/strings.xml
index b22655a..443f666 100644
--- a/packages/SystemUI/res-keyguard/values-as/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-as/strings.xml
@@ -64,9 +64,9 @@
     <string name="kg_sim_unlock_progress_dialog_message" msgid="4251352015304070326">"ছিম কার্ড আনলক কৰি থকা হৈছে…"</string>
     <string name="kg_invalid_sim_pin_hint" msgid="2762202646949552978">"৪টাৰ পৰা ৮টা সংখ্যাযুক্ত এটা পিন লিখক।"</string>
     <string name="kg_invalid_sim_puk_hint" msgid="5319756880543857694">"PUK ক\'ডটো ৮টা বা তাতকৈ অধিক সংখ্যা থকা হ\'ব লাগিব।"</string>
-    <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="544687656831558971">"আপুনি আপোনাৰ পিন <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string>
+    <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="544687656831558971">"আপুনি আপোনাৰ পিন <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string>
     <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="190984061975729494">"আপুনি আপোনাৰ পাছৱৰ্ড <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string>
-    <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="4252405904570284368">"আপুনি আপোনাৰ আনলক আৰ্হি <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ আঁকিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string>
+    <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="4252405904570284368">"আপুনি আপোনাৰ আনলক আৰ্হি <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ আঁকিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string>
     <string name="kg_password_wrong_pin_code_pukked" msgid="8047350661459040581">"ছিমৰ ভুল পিন ক\'ড, আপোনাৰ ডিভাইচটো আনলক কৰিবলৈ আপুনি এতিয়া আপোনাৰ বাহকৰ সৈতে যোগাযোগ কৰিবই লাগিব।"</string>
     <string name="kg_password_wrong_pin_code" msgid="5629415765976820357">"{count,plural, =1{ছিমৰ পিন ক’ড ভুল হৈছে, আপোনাৰ ডিভাইচ আনলক কৰিবলৈ আপোনাৰ বাহকৰ লগত যোগাযোগ কৰিবই লগা হোৱাৰ পূৰ্বে আপোনাৰ ওচৰত # টা প্ৰয়াস বাকী আছে।}one{ছিমৰ পিন ক’ড ভুল হৈছে, আপোনাৰ ওচৰত # টা প্ৰয়াস বাকী আছে। }other{ছিমৰ পিন ক’ড ভুল হৈছে, আপোনাৰ ওচৰত # টা প্ৰয়াস বাকী আছে। }}"</string>
     <string name="kg_password_wrong_puk_code_dead" msgid="3698285357028468617">"ছিম ব্যৱহাৰযোগ্য নহয়। আপোনাৰ বাহকৰ সৈতে যোগাযোগ কৰক।"</string>
@@ -75,12 +75,15 @@
     <string name="kg_password_puk_failed" msgid="6778867411556937118">"ছিম PUKৰ জৰিয়তে আনলক কৰিব পৰা নগ\'ল!"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"ইনপুট পদ্ধতি সলনি কৰক"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"এয়াৰপ্লে’ন ম’ড"</string>
-    <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পিছত আৰ্হি দিয়াটো বাধ্যতামূলক"</string>
-    <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পিছত পিন দিয়াটো বাধ্যতামূলক"</string>
-    <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পিছত পাছৱৰ্ড দিয়াটো বাধ্যতামূলক"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"অতিৰিক্ত সুৰক্ষাৰ বাবে আর্হি দিয়াটো বাধ্যতামূলক"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"অতিৰিক্ত সুৰক্ষাৰ বাবে পিন দিয়াটো বাধ্যতামূলক"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"অতিৰিক্ত সুৰক্ষাৰ বাবে পাছৱর্ড দিয়াটো বাধ্যতামূলক"</string>
+    <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত আৰ্হি দিয়াটো বাধ্যতামূলক"</string>
+    <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত পিন দিয়াটো বাধ্যতামূলক"</string>
+    <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত পাছৱৰ্ড দিয়াটো বাধ্যতামূলক"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"প্ৰশাসকে ডিভাইচ লক কৰি ৰাখিছে"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ডিভাইচটো মেনুৱেলভাৱে লক কৰা হৈছিল"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"চিনাক্ত কৰিব পৰা নাই"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ডিফ’ল্ট"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"বাবল"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"এনাল’গ"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-az/strings.xml b/packages/SystemUI/res-keyguard/values-az/strings.xml
index 6ec1061..e125697 100644
--- a/packages/SystemUI/res-keyguard/values-az/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-az/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Cihaz yenidən başladıqdan sonra model tələb olunur"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Cihaz yeniden başladıqdan sonra PIN tələb olunur"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Cihaz yeniden başladıqdan sonra parol tələb olunur"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Əlavə təhlükəsizlik üçün model tələb olunur"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Əlavə təhlükəsizlik üçün PIN tələb olunur"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Əlavə təhlükəsizlik üçün parol tələb olunur"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Cihaz admin tərəfindən kilidlənib"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Cihaz əl ilə kilidləndi"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Tanınmır"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Defolt"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Qabarcıq"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analoq"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
index 13d6613..f0d1ef2 100644
--- a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Treba da unesete šablon kada se uređaj ponovo pokrene"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Treba da unesete PIN kada se uređaj ponovo pokrene"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Treba da unesete lozinku kada se uređaj ponovo pokrene"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Treba da unesete šablon radi dodatne bezbednosti"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Treba da unesete PIN radi dodatne bezbednosti"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Treba da unesete lozinku radi dodatne bezbednosti"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administrator je zaključao uređaj"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Uređaj je ručno zaključan"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nije prepoznat"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Podrazumevani"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Mehurići"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogni"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-be/strings.xml b/packages/SystemUI/res-keyguard/values-be/strings.xml
index 616d31a..e1af3ece 100644
--- a/packages/SystemUI/res-keyguard/values-be/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-be/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Пасля перазапуску прылады патрабуецца ўзор"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Пасля перазапуску прылады патрабуецца PIN-код"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Пасля перазапуску прылады патрабуецца пароль"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Для забеспячэння дадатковай бяспекі патрабуецца ўзор"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Для забеспячэння дадатковай бяспекі патрабуецца PIN-код"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Для забеспячэння дадатковай бяспекі патрабуецца пароль"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Прылада заблакіравана адміністратарам"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Прылада была заблакіравана ўручную"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Не распазнана"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Стандартны"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Бурбалкі"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Са стрэлкамі"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-bg/strings.xml b/packages/SystemUI/res-keyguard/values-bg/strings.xml
index 366a7f4..0b4417a 100644
--- a/packages/SystemUI/res-keyguard/values-bg/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bg/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"След рестартиране на устройството се изисква фигура"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"След рестартиране на устройството се изисква ПИН код"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"След рестартиране на устройството се изисква парола"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"За допълнителна сигурност се изисква фигура"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"За допълнителна сигурност се изисква ПИН код"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"За допълнителна сигурност се изисква парола"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Устройството е заключено от администратора"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Устройството бе заключено ръчно"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Не е разпознато"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Стандартен"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Балонен"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Аналогов"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-bn/strings.xml b/packages/SystemUI/res-keyguard/values-bn/strings.xml
index c20be5d..4851579 100644
--- a/packages/SystemUI/res-keyguard/values-bn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bn/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ডিভাইসটি পুনরায় চালু হওয়ার পর প্যাটার্নের প্রয়োজন হবে"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ডিভাইসটি পুনরায় চালু হওয়ার পর পিন প্রয়োজন হবে"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ডিভাইসটি পুনরায় চালু হওয়ার পর পাসওয়ার্ডের প্রয়োজন হবে"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"অতিরিক্ত সুরক্ষার জন্য প্যাটার্ন দেওয়া প্রয়োজন"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"অতিরিক্ত সুরক্ষার জন্য পিন দেওয়া প্রয়োজন"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"অতিরিক্ত সুরক্ষার জন্য পাসওয়ার্ড দেওয়া প্রয়োজন"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"প্রশাসক ডিভাইসটি লক করেছেন"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ডিভাইসটিকে ম্যানুয়ালি লক করা হয়েছে"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"শনাক্ত করা যায়নি"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ডিফল্ট"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"বাবল"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"অ্যানালগ"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-bs/strings.xml b/packages/SystemUI/res-keyguard/values-bs/strings.xml
index f1c00a9..4705b4d9 100644
--- a/packages/SystemUI/res-keyguard/values-bs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bs/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Potreban je uzorak nakon što se uređaj ponovo pokrene"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Potreban je PIN nakon što se uređaj ponovo pokrene"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Potrebna je lozinka nakon što se uređaj ponovo pokrene"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Uzorak je potreban radi dodatne sigurnosti"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN je potreban radi dodatne sigurnosti"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Lozinka je potrebna radi dodatne sigurnosti"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Uređaj je zaključao administrator"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Uređaj je ručno zaključan"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nije prepoznato"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Zadano"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Mjehurići"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogni"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ca/strings.xml b/packages/SystemUI/res-keyguard/values-ca/strings.xml
index 709407c..284eaeb 100644
--- a/packages/SystemUI/res-keyguard/values-ca/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ca/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Cal introduir el patró quan es reinicia el dispositiu"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Cal introduir el PIN quan es reinicia el dispositiu"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Cal introduir la contrasenya quan es reinicia el dispositiu"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Cal introduir el patró per disposar de més seguretat"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Cal introduir el PIN per disposar de més seguretat"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Cal introduir la contrasenya per disposar de més seguretat"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"L\'administrador ha bloquejat el dispositiu"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"El dispositiu s\'ha bloquejat manualment"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"No s\'ha reconegut"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Predeterminada"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bombolla"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analògica"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-cs/strings.xml b/packages/SystemUI/res-keyguard/values-cs/strings.xml
index a44658c..6b4f607 100644
--- a/packages/SystemUI/res-keyguard/values-cs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-cs/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po restartování zařízení je vyžadováno gesto"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po restartování zařízení je vyžadován kód PIN"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po restartování zařízení je vyžadováno heslo"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pro ještě lepší zabezpečení je vyžadováno gesto"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Pro ještě lepší zabezpečení je vyžadován kód PIN"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Pro ještě lepší zabezpečení je vyžadováno heslo"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Zařízení je uzamknuto administrátorem"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Zařízení bylo ručně uzamčeno"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nerozpoznáno"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Výchozí"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bublina"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogové"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-da/strings.xml b/packages/SystemUI/res-keyguard/values-da/strings.xml
index 331c355..85238df 100644
--- a/packages/SystemUI/res-keyguard/values-da/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-da/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Du skal angive et mønster, når du har genstartet enheden"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Der skal angives en pinkode efter genstart af enheden"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Der skal angives en adgangskode efter genstart af enheden"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Der kræves et mønster som ekstra beskyttelse"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Der kræves en pinkode som ekstra beskyttelse"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Der kræves en adgangskode som ekstra beskyttelse"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Enheden er blevet låst af administratoren"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Enheden blev låst manuelt"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Ikke genkendt"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Standard"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Boble"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-de/strings.xml b/packages/SystemUI/res-keyguard/values-de/strings.xml
index c19b357..18befed 100644
--- a/packages/SystemUI/res-keyguard/values-de/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-de/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Nach dem Neustart des Geräts ist die Eingabe des Musters erforderlich"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Nach dem Neustart des Geräts ist die Eingabe der PIN erforderlich"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Nach dem Neustart des Geräts ist die Eingabe des Passworts erforderlich"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Zur Verbesserung der Sicherheit ist ein Muster erforderlich"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Zur Verbesserung der Sicherheit ist eine PIN erforderlich"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Zur Verbesserung der Sicherheit ist ein Passwort erforderlich"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Gerät vom Administrator gesperrt"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Gerät manuell gesperrt"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nicht erkannt"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Standard"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-el/strings.xml b/packages/SystemUI/res-keyguard/values-el/strings.xml
index 1d6ec82..65b84486 100644
--- a/packages/SystemUI/res-keyguard/values-el/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-el/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Απαιτείται μοτίβο μετά από την επανεκκίνηση της συσκευής"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Απαιτείται PIN μετά από την επανεκκίνηση της συσκευής"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Απαιτείται κωδικός πρόσβασης μετά από την επανεκκίνηση της συσκευής"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Απαιτείται μοτίβο για πρόσθετη ασφάλεια"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Απαιτείται PIN για πρόσθετη ασφάλεια"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Απαιτείται κωδικός πρόσβασης για πρόσθετη ασφάλεια"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Η συσκευή κλειδώθηκε από τον διαχειριστή"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Η συσκευή κλειδώθηκε με μη αυτόματο τρόπο"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Δεν αναγνωρίστηκε"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Προεπιλογή"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Συννεφάκι"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Αναλογικό"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
index 2b78f96..588f1b5 100644
--- a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pattern required for additional security"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN required for additional security"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Password required for additional security"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
index e1c2532..08fc8d6 100644
--- a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pattern required for additional security"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN required for additional security"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Password required for additional security"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
index 2b78f96..588f1b5 100644
--- a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pattern required for additional security"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN required for additional security"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Password required for additional security"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
index 2b78f96..588f1b5 100644
--- a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pattern required for additional security"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN required for additional security"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Password required for additional security"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
index 9052e4f..a23aeb0 100644
--- a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
@@ -78,9 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‎‎‏‏‎‎‏‎‏‎‎‎‏‎‎Pattern required after device restarts‎‏‎‎‏‎"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‎‎‎‎‏‎‎‎‏‎‎‎‏‎‏‎‏‏‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‎‎‏‏‎‏‎‏‏‎‎‏‏‎‏‏‎‏‏‏‎‎‎‎PIN required after device restarts‎‏‎‎‏‎"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‎‏‎‎‎‏‏‏‎‎‏‎‎‏‎‎‎‎‎‏‏‏‎‎‎‏‏‎‎‎‎‏‎‎Password required after device restarts‎‏‎‎‏‎"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‎‏‎‏‎‏‎‎‏‎‎‏‎‏‎‎‏‎‏‎‏‏‏‏‎‎‎‎‎‏‎‏‏‏‎‎‏‎‏‏‎‎‏‎‎‎‏‎Pattern required for additional security‎‏‎‎‏‎"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‎‎‎‏‏‎‎‎‏‎‏‏‎‏‎‎‎‎‎‏‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‎‏‎‎‎‎‏‎‎‎‎‎‏‎‎‎‏‎PIN required for additional security‎‏‎‎‏‎"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‏‏‎‏‏‏‎‎‎‏‏‎‎‎‏‏‏‎‎‎‏‎‏‏‎‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‏‎‏‏‎‎Password required for additional security‎‏‎‎‏‎"</string>
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‎‎‎‏‎‎‏‎‎‎‏‏‎‏‏‏‎‏‎‏‏‏‏‏‎‏‎‏‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‏‎‏‏‏‎‎‏‏‎For additional security, use pattern instead‎‏‎‎‏‎"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‏‏‎‎‏‎‏‏‏‏‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‏‎‏‎‎‏‏‎‎‎‎‎For additional security, use PIN instead‎‏‎‎‏‎"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‏‎‏‎‎‎‎‏‏‏‏‏‏‎‎‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‏‎‎‎‏‏‎‏‎‎‎‎‏‏‏‏‏‎‏‎‎For additional security, use password instead‎‏‎‎‏‎"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‏‏‎‏‏‎‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‎‏‎‏‏‏‎‏‏‎‏‏‏‏‎‎‏‎‎‎‏‎‎‏‏‎‎‎‎‏‎‏‎Device locked by admin‎‏‎‎‏‎"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‎‏‏‎‎‎‎‎‏‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‏‎‎‎‎‏‏‏‏‎‎‏‎‎‎‎‎Device was locked manually‎‏‎‎‏‎"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‎‎‎‎‎‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‎‎‏‏‏‏‎‎‎‎‏‏‎‏‎‏‏‎‎‎‎Not recognized‎‏‎‎‏‎"</string>
@@ -90,4 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‎‎‏‏‎‎‎‏‎‎‏‏‎‎‏‎‏‎‏‏‎‏‏‎‎‎‎‎‎‎‎‏‎‎‏‏‎‎‎‎Default‎‏‎‎‏‎"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‏‏‎‎‎‎‎‏‎‏‎‏‏‎‎‎‏‎‏‏‏‎‏‎‏‎‎‏‏‏‏‏‏‎‎‏‏‏‏‏‎‎‏‏‎‏‎‏‏‏‏‎‏‎Bubble‎‏‎‎‏‎"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‎‎‏‏‏‎‏‏‎‎‏‏‏‎‏‏‏‎‎‎‎‎‏‏‎‎‎‎‏‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‎‎‎‎‎‎‎‏‎Analog‎‏‎‎‏‎"</string>
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‏‎‎‎‎‏‏‏‎‎‎‎‏‎‎‏‎‏‎‎‎‎‏‎‏‎‏‎‎‏‎‏‏‎‏‏‏‏‎Unlock your device to continue‎‏‎‎‏‎"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
index 9dc054a..c71a678 100644
--- a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Se requiere el patrón después de reiniciar el dispositivo"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Se requiere el PIN después de reiniciar el dispositivo"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Se requiere la contraseña después de reiniciar el dispositivo"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Se requiere el patrón por razones de seguridad"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Se requiere el PIN por razones de seguridad"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Se requiere la contraseña por razones de seguridad"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado por el administrador"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"El dispositivo se bloqueó de forma manual"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"No se reconoció"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Predeterminado"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Burbuja"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-es/strings.xml b/packages/SystemUI/res-keyguard/values-es/strings.xml
index f9f0452..c6ee698 100644
--- a/packages/SystemUI/res-keyguard/values-es/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Debes introducir el patrón después de reiniciar el dispositivo"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Debes introducir el PIN después de reiniciar el dispositivo"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Debes introducir la contraseña después de reiniciar el dispositivo"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Debes introducir el patrón como medida de seguridad adicional"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Debes introducir el PIN como medida de seguridad adicional"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Debes introducir la contraseña como medida de seguridad adicional"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado por el administrador"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"El dispositivo se ha bloqueado manualmente"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"No se reconoce"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Predeterminado"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Burbuja"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-et/strings.xml b/packages/SystemUI/res-keyguard/values-et/strings.xml
index dceb78e..071ede8 100644
--- a/packages/SystemUI/res-keyguard/values-et/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-et/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pärast seadme taaskäivitamist tuleb sisestada muster"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pärast seadme taaskäivitamist tuleb sisestada PIN-kood"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pärast seadme taaskäivitamist tuleb sisestada parool"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Lisaturvalisuse huvides tuleb sisestada muster"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Lisaturvalisuse huvides tuleb sisestada PIN-kood"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Lisaturvalisuse huvides tuleb sisestada parool"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administraator lukustas seadme"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Seade lukustati käsitsi"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Ei tuvastatud"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Vaikenumbrilaud"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Mull"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analoog"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-eu/strings.xml b/packages/SystemUI/res-keyguard/values-eu/strings.xml
index 8431268..9b8e65b 100644
--- a/packages/SystemUI/res-keyguard/values-eu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-eu/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Eredua marraztu beharko duzu gailua berrabiarazten denean"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PINa idatzi beharko duzu gailua berrabiarazten denean"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pasahitza idatzi beharko duzu gailua berrabiarazten denean"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Eredua behar da gailua babestuago izateko"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PINa behar da gailua babestuago izateko"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Pasahitza behar da gailua babestuago izateko"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administratzaileak blokeatu egin du gailua"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Eskuz blokeatu da gailua"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Ez da ezagutu"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Lehenetsia"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Puxikak"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogikoa"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-fa/strings.xml b/packages/SystemUI/res-keyguard/values-fa/strings.xml
index 37bb260..3583f1e 100644
--- a/packages/SystemUI/res-keyguard/values-fa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fa/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"بعد از بازنشانی دستگاه باید الگو وارد شود"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"بعد از بازنشانی دستگاه باید پین وارد شود"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"بعد از بازنشانی دستگاه باید گذرواژه وارد شود"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"برای ایمنی بیشتر باید الگو وارد شود"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"برای ایمنی بیشتر باید پین وارد شود"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"برای ایمنی بیشتر باید گذرواژه وارد شود"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"دستگاه توسط سرپرست سیستم قفل شده است"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"دستگاه به‌صورت دستی قفل شده است"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"شناسایی نشد"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"پیش‌فرض"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"حباب"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"آنالوگ"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-fi/strings.xml b/packages/SystemUI/res-keyguard/values-fi/strings.xml
index f8cec42..a0ac6df 100644
--- a/packages/SystemUI/res-keyguard/values-fi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fi/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Kuvio vaaditaan laitteen uudelleenkäynnistyksen jälkeen."</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN-koodi vaaditaan laitteen uudelleenkäynnistyksen jälkeen."</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Salasana vaaditaan laitteen uudelleenkäynnistyksen jälkeen."</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Kuvio vaaditaan suojauksen parantamiseksi."</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN-koodi vaaditaan suojauksen parantamiseksi."</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Salasana vaaditaan suojauksen parantamiseksi."</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Järjestelmänvalvoja lukitsi laitteen."</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Laite lukittiin manuaalisesti"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Ei tunnistettu"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Oletus"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Kupla"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analoginen"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
index 077fe11..66fd7c0 100644
--- a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
@@ -78,9 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Le schéma est exigé après le redémarrage de l\'appareil"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Le NIP est exigé après le redémarrage de l\'appareil"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Le mot de passe est exigé après le redémarrage de l\'appareil"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Le schéma est exigé pour plus de sécurité"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Le NIP est exigé pour plus de sécurité"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Le mot de passe est exigé pour plus de sécurité"</string>
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Pour plus de sécurité, utilisez plutôt un schéma"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Pour plus de sécurité, utilisez plutôt un NIP"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Pour plus de sécurité, utilisez plutôt un mot de passe"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"L\'appareil a été verrouillé par l\'administrateur"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"L\'appareil a été verrouillé manuellement"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Doigt non reconnu"</string>
@@ -90,4 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Par défaut"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bulle"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogique"</string>
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Déverrouiller votre appareil pour continuer"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-fr/strings.xml b/packages/SystemUI/res-keyguard/values-fr/strings.xml
index 45dadc1..ec00ba3 100644
--- a/packages/SystemUI/res-keyguard/values-fr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Veuillez dessiner le schéma après le redémarrage de l\'appareil"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Veuillez saisir le code après le redémarrage de l\'appareil"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Veuillez saisir le mot de passe après le redémarrage de l\'appareil"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Veuillez dessiner le schéma pour renforcer la sécurité"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Veuillez saisir le code pour renforcer la sécurité"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Veuillez saisir le mot de passe pour renforcer la sécurité"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Appareil verrouillé par l\'administrateur"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Appareil verrouillé manuellement"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Non reconnu"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Par défaut"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bulle"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogique"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-gl/strings.xml b/packages/SystemUI/res-keyguard/values-gl/strings.xml
index 4fbdd67..a3f8e86 100644
--- a/packages/SystemUI/res-keyguard/values-gl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gl/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"É necesario o padrón despois do reinicio do dispositivo"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"É necesario o PIN despois do reinicio do dispositivo"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"É necesario o contrasinal despois do reinicio do dispositivo"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"É necesario o padrón para obter seguranza adicional"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"É necesario o PIN para obter seguranza adicional"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"É necesario o contrasinal para obter seguranza adicional"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"O administrador bloqueou o dispositivo"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"O dispositivo bloqueouse manualmente"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Non se recoñeceu"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Predeterminado"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Burbulla"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analóxico"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-gu/strings.xml b/packages/SystemUI/res-keyguard/values-gu/strings.xml
index 6caac8a..c97fe01 100644
--- a/packages/SystemUI/res-keyguard/values-gu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gu/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ઉપકરણનો પુનઃપ્રારંભ થાય તે પછી પૅટર્ન જરૂરી છે"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ઉપકરણનો પુનઃપ્રારંભ થાય તે પછી પિન જરૂરી છે"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ઉપકરણનો પુનઃપ્રારંભ થાય તે પછી પાસવર્ડ જરૂરી છે"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"વધારાની સુરક્ષા માટે પૅટર્ન જરૂરી છે"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"વધારાની સુરક્ષા માટે પિન જરૂરી છે"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"વધારાની સુરક્ષા માટે પાસવર્ડ જરૂરી છે"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"વ્યવસ્થાપકે ઉપકરણ લૉક કરેલું છે"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ઉપકરણ મેન્યુઅલી લૉક કર્યું હતું"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"ઓળખાયેલ નથી"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ડિફૉલ્ટ"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"બબલ"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"એનાલોગ"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-hi/strings.xml b/packages/SystemUI/res-keyguard/values-hi/strings.xml
index 627576e..1283004 100644
--- a/packages/SystemUI/res-keyguard/values-hi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hi/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"डिवाइस फिर से चालू होने के बाद पैटर्न ज़रूरी है"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"डिवाइस फिर से चालू होने के बाद पिन ज़रूरी है"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"डिवाइस फिर से चालू होने के बाद पासवर्ड ज़रूरी है"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"अतिरिक्त सुरक्षा के लिए पैटर्न ज़रूरी है"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"अतिरिक्त सुरक्षा के लिए पिन ज़रूरी है"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"अतिरिक्त सुरक्षा के लिए पासवर्ड ज़रूरी है"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"व्यवस्थापक ने डिवाइस को लॉक किया है"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"डिवाइस को मैन्युअल रूप से लॉक किया गया था"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"पहचान नहीं हो पाई"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"डिफ़ॉल्ट"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"बबल"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"एनालॉग"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-hr/strings.xml b/packages/SystemUI/res-keyguard/values-hr/strings.xml
index 8b1b504..7a14a80 100644
--- a/packages/SystemUI/res-keyguard/values-hr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hr/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Nakon ponovnog pokretanja uređaja morate unijeti uzorak"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Nakon ponovnog pokretanja uređaja morate unijeti PIN"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Nakon ponovnog pokretanja uređaja morate unijeti zaporku"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Unesite uzorak radi dodatne sigurnosti"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Unesite PIN radi dodatne sigurnosti"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Unesite zaporku radi dodatne sigurnosti"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administrator je zaključao uređaj"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Uređaj je ručno zaključan"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nije prepoznato"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Zadano"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Mjehurić"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogni"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-hu/strings.xml b/packages/SystemUI/res-keyguard/values-hu/strings.xml
index 6b75e72..a4fbf53 100644
--- a/packages/SystemUI/res-keyguard/values-hu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hu/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Az eszköz újraindítását követően meg kell adni a mintát"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Az eszköz újraindítását követően meg kell adni a PIN-kódot"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Az eszköz újraindítását követően meg kell adni a jelszót"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"A nagyobb biztonság érdekében minta szükséges"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"A nagyobb biztonság érdekében PIN-kód szükséges"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"A nagyobb biztonság érdekében jelszó szükséges"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"A rendszergazda zárolta az eszközt"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Az eszközt manuálisan lezárták"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nem ismerhető fel"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Alapértelmezett"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Buborék"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analóg"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-hy/strings.xml b/packages/SystemUI/res-keyguard/values-hy/strings.xml
index 3412026..086eeb9 100644
--- a/packages/SystemUI/res-keyguard/values-hy/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hy/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Սարքը վերագործարկելուց հետո անհրաժեշտ է մուտքագրել նախշը"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Սարքը վերագործարկելուց հետո անհրաժեշտ է մուտքագրել PIN կոդը"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Սարքը վերագործարկելուց հետո անհրաժեշտ է մուտքագրել գաղտնաբառը"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Անվտանգության նկատառումներից ելնելով անհրաժեշտ է մուտքագրել նախշը"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Անվտանգության նկատառումներից ելնելով անհրաժեշտ է մուտքագրել PIN կոդը"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Անվտանգության նկատառումներից ելնելով անհրաժեշտ է մուտքագրել գաղտնաբառը"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Սարքը կողպված է ադմինիստրատորի կողմից"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Սարքը կողպվել է ձեռքով"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Չհաջողվեց ճանաչել"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Կանխադրված"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Պղպջակ"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Անալոգային"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-in/strings.xml b/packages/SystemUI/res-keyguard/values-in/strings.xml
index 1afb791..b43a032 100644
--- a/packages/SystemUI/res-keyguard/values-in/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-in/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pola diperlukan setelah perangkat dimulai ulang"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN diperlukan setelah perangkat dimulai ulang"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Sandi diperlukan setelah perangkat dimulai ulang"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pola diperlukan untuk keamanan tambahan"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN diperlukan untuk keamanan tambahan"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Sandi diperlukan untuk keamanan tambahan"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Perangkat dikunci oleh admin"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Perangkat dikunci secara manual"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Tidak dikenali"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Balon"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-is/strings.xml b/packages/SystemUI/res-keyguard/values-is/strings.xml
index 6abdc82..8bad961 100644
--- a/packages/SystemUI/res-keyguard/values-is/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-is/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Mynsturs er krafist þegar tækið er endurræst"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN-númers er krafist þegar tækið er endurræst"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Aðgangsorðs er krafist þegar tækið er endurræst"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Mynsturs er krafist af öryggisástæðum"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN-númers er krafist af öryggisástæðum"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Aðgangsorðs er krafist af öryggisástæðum"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Kerfisstjóri læsti tæki"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Tækinu var læst handvirkt"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Þekktist ekki"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Sjálfgefið"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Blaðra"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Með vísum"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-it/strings.xml b/packages/SystemUI/res-keyguard/values-it/strings.xml
index 9fed5f7..186177ff 100644
--- a/packages/SystemUI/res-keyguard/values-it/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-it/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Sequenza obbligatoria dopo il riavvio del dispositivo"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN obbligatorio dopo il riavvio del dispositivo"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password obbligatoria dopo il riavvio del dispositivo"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Sequenza obbligatoria per maggiore sicurezza"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN obbligatorio per maggiore sicurezza"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Password obbligatoria per maggiore sicurezza"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloccato dall\'amministratore"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Il dispositivo è stato bloccato manualmente"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Non riconosciuto"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Predefinito"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bolla"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogico"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-iw/strings.xml b/packages/SystemUI/res-keyguard/values-iw/strings.xml
index b5b1c53..aab4206 100644
--- a/packages/SystemUI/res-keyguard/values-iw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-iw/strings.xml
@@ -78,9 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"יש להזין את קו ביטול הנעילה לאחר הפעלה מחדש של המכשיר"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"צריך להזין קוד אימות לאחר הפעלה מחדש של המכשיר"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"יש להזין סיסמה לאחר הפעלה מחדש של המכשיר"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"יש להזין את קו ביטול הנעילה כדי להגביר את רמת האבטחה"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"יש להזין קוד אימות כדי להגביר את רמת האבטחה"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"יש להזין סיסמה כדי להגביר את רמת האבטחה"</string>
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"כדי להגביר את רמת האבטחה, כדאי להשתמש בקו ביטול נעילה במקום זאת"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"כדי להגביר את רמת האבטחה, כדאי להשתמש בקוד אימות במקום זאת"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"כדי להגביר את רמת האבטחה, כדאי להשתמש בסיסמה במקום זאת"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"המנהל של המכשיר נהל אותו"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"המכשיר ננעל באופן ידני"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"לא זוהתה"</string>
@@ -90,4 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ברירת מחדל"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"בועה"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"אנלוגי"</string>
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"צריך לבטל את הנעילה של המכשיר כדי להמשיך"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ja/strings.xml b/packages/SystemUI/res-keyguard/values-ja/strings.xml
index afe0159..1a4fb0b 100644
--- a/packages/SystemUI/res-keyguard/values-ja/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ja/strings.xml
@@ -78,9 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"デバイスの再起動後はパターンの入力が必要となります"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"デバイスの再起動後は PIN の入力が必要となります"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"デバイスの再起動後はパスワードの入力が必要となります"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"追加の確認のためパターンが必要です"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"追加の確認のため PIN が必要です"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"追加の確認のためパスワードが必要です"</string>
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"セキュリティを強化するには代わりにパターンを使用してください"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"セキュリティを強化するには代わりに PIN を使用してください"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"セキュリティを強化するには代わりにパスワードを使用してください"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"デバイスは管理者によりロックされています"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"デバイスは手動でロックされました"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"認識されませんでした"</string>
@@ -90,4 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"デフォルト"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"バブル"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"アナログ"</string>
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"続行するにはデバイスのロックを解除してください"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ka/strings.xml b/packages/SystemUI/res-keyguard/values-ka/strings.xml
index b32caa7..123cc39 100644
--- a/packages/SystemUI/res-keyguard/values-ka/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ka/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"მოწყობილობის გადატვირთვის შემდეგ საჭიროა ნიმუშის დახატვა"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"მოწყობილობის გადატვირთვის შემდეგ საჭიროა PIN-კოდის შეყვანა"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"მოწყობილობის გადატვირთვის შემდეგ საჭიროა პაროლის შეყვანა"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"დამატებითი უსაფრთხოებისთვის საჭიროა ნიმუშის დახატვა"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"დამატებითი უსაფრთხოებისთვის საჭიროა PIN-კოდის შეყვანა"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"დამატებითი უსაფრთხოებისთვის საჭიროა პაროლის შეყვანა"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"მოწყობილობა ჩაკეტილია ადმინისტრატორის მიერ"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"მოწყობილობა ხელით ჩაიკეტა"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"არ არის ამოცნობილი"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ნაგულისხმევი"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"ბუშტი"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"ანალოგური"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-kk/strings.xml b/packages/SystemUI/res-keyguard/values-kk/strings.xml
index d6d5bcd..8daca5c 100644
--- a/packages/SystemUI/res-keyguard/values-kk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kk/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Құрылғы қайта іске қосылғаннан кейін, өрнекті енгізу қажет"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Құрылғы қайта іске қосылғаннан кейін, PIN кодын енгізу қажет"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Құрылғы қайта іске қосылғаннан кейін, құпия сөзді енгізу қажет"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Қауіпсіздікті күшейту үшін өрнекті енгізу қажет"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Қауіпсіздікті күшейту үшін PIN кодын енгізу қажет"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Қауіпсіздікті күшейту үшін құпия сөзді енгізу қажет"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Құрылғыны әкімші құлыптаған"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Құрылғы қолмен құлыпталды"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Танылмады"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Әдепкі"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Көпіршік"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Аналогтық"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-km/strings.xml b/packages/SystemUI/res-keyguard/values-km/strings.xml
index 00bfe05..73f507c 100644
--- a/packages/SystemUI/res-keyguard/values-km/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-km/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"តម្រូវឲ្យប្រើលំនាំ បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"តម្រូវឲ្យបញ្ចូលកូដ PIN បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"តម្រូវឲ្យបញ្ចូលពាក្យសម្ងាត់ បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"តម្រូវឲ្យប្រើលំនាំ ដើម្បីទទួលបានសវុត្ថិភាពបន្ថែម"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"តម្រូវឲ្យបញ្ចូលកូដ PIN ដើម្បីទទួលបានសុវត្ថិភាពបន្ថែម"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"តម្រូវឲ្យបញ្ចូលពាក្យសម្ងាត់ ដើម្បីទទួលបានសុវត្ថិភាពបន្ថែម"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ឧបករណ៍​ត្រូវបាន​ចាក់សោ​ដោយអ្នក​គ្រប់គ្រង"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ឧបករណ៍ត្រូវបានចាក់សោដោយអ្នកប្រើផ្ទាល់"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"មិនអាចសម្គាល់បានទេ"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"លំនាំដើម"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"ពពុះ"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"អាណាឡូក"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-kn/strings.xml b/packages/SystemUI/res-keyguard/values-kn/strings.xml
index 80a98e6..c279cea 100644
--- a/packages/SystemUI/res-keyguard/values-kn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kn/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ಸಾಧನ ಮರುಪ್ರಾರಂಭಗೊಂಡ ನಂತರ ಪ್ಯಾಟರ್ನ್ ಅಗತ್ಯವಿರುತ್ತದೆ"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ಸಾಧನ ಮರುಪ್ರಾರಂಭಗೊಂಡ ನಂತರ ಪಿನ್ ಅಗತ್ಯವಿರುತ್ತದೆ"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ಸಾಧನ ಮರುಪ್ರಾರಂಭಗೊಂಡ ನಂತರ ಪಾಸ್‌ವರ್ಡ್ ಅಗತ್ಯವಿರುತ್ತದೆ"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ಹೆಚ್ಚುವರಿ ಭದ್ರತೆಗೆ ಪ್ಯಾಟರ್ನ್ ಅಗತ್ಯವಿದೆ"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ಹೆಚ್ಚುವರಿ ಭದ್ರತೆಗೆ ಪಿನ್ ಅಗತ್ಯವಿದೆ"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ಹೆಚ್ಚುವರಿ ಭದ್ರತೆಗಾಗಿ ಪಾಸ್‌ವರ್ಡ್ ಅಗತ್ಯವಿದೆ"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ನಿರ್ವಾಹಕರು ಸಾಧನವನ್ನು ಲಾಕ್ ಮಾಡಿದ್ದಾರೆ"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ಸಾಧನವನ್ನು ಹಸ್ತಚಾಲಿತವಾಗಿ ಲಾಕ್‌ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"ಗುರುತಿಸಲಾಗಿಲ್ಲ"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ಡೀಫಾಲ್ಟ್"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"ಬಬಲ್"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"ಅನಲಾಗ್"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ko/strings.xml b/packages/SystemUI/res-keyguard/values-ko/strings.xml
index b952f1b..4c058ed 100644
--- a/packages/SystemUI/res-keyguard/values-ko/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ko/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"기기가 다시 시작되면 패턴이 필요합니다."</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"기기가 다시 시작되면 PIN이 필요합니다."</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"기기가 다시 시작되면 비밀번호가 필요합니다."</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"보안 강화를 위해 패턴이 필요합니다."</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"보안 강화를 위해 PIN이 필요합니다."</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"보안 강화를 위해 비밀번호가 필요합니다."</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"관리자가 기기를 잠갔습니다."</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"기기가 수동으로 잠금 설정되었습니다."</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"인식할 수 없음"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"기본"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"버블"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"아날로그"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ky/strings.xml b/packages/SystemUI/res-keyguard/values-ky/strings.xml
index 485337d..7c7099e 100644
--- a/packages/SystemUI/res-keyguard/values-ky/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ky/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Түзмөк кайра күйгүзүлгөндөн кийин графикалык ачкычты тартуу талап кылынат"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Түзмөк кайра күйгүзүлгөндөн кийин PIN-кодду киргизүү талап кылынат"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Түзмөк кайра күйгүзүлгөндөн кийин сырсөздү киргизүү талап кылынат"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Коопсуздукту бекемдөө үчүн графикалык ачкыч талап кылынат"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Коопсуздукту бекемдөө үчүн PIN-код талап кылынат"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Коопсуздукту бекемдөө үчүн сырсөз талап кылынат"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Түзмөктү администратор кулпулап койгон"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Түзмөк кол менен кулпуланды"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Таанылган жок"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Демейки"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Көбүк"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Аналог"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-lo/strings.xml b/packages/SystemUI/res-keyguard/values-lo/strings.xml
index 17584b5..5a6df42 100644
--- a/packages/SystemUI/res-keyguard/values-lo/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lo/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ຈຳເປັນຕ້ອງມີແບບຮູບປົດລັອກຫຼັງຈາກອຸປະກອນເລີ່ມລະບົບໃໝ່"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ຈຳເປັນຕ້ອງມີ PIN ຫຼັງຈາກອຸປະກອນເລີ່ມລະບົບໃໝ່"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ຈຳເປັນຕ້ອງມີລະຫັດຜ່ານຫຼັງຈາກອຸປະກອນເລີ່ມລະບົບໃໝ່"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ຈຳເປັນຕ້ອງມີແບບຮູບເພື່ອຄວາມປອດໄພເພີ່ມເຕີມ"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ຈຳເປັນຕ້ອງມີ PIN ເພື່ອຄວາມປອດໄພເພີ່ມເຕີມ"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ຈຳເປັນຕ້ອງມີລະຫັດຜ່ານເພື່ອຄວາມປອດໄພເພີ່ມເຕີມ"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ອຸປະກອນຖືກລັອກໂດຍຜູ້ເບິ່ງແຍງລະບົບ"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ອຸປະກອນຖືກສັ່ງໃຫ້ລັອກ"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"ບໍ່ຮູ້ຈັກ"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ຄ່າເລີ່ມຕົ້ນ"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"ຟອງ"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"ໂມງເຂັມ"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-lt/strings.xml b/packages/SystemUI/res-keyguard/values-lt/strings.xml
index a066a66..4d98fd1 100644
--- a/packages/SystemUI/res-keyguard/values-lt/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lt/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Iš naujo paleidus įrenginį būtinas atrakinimo piešinys"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Iš naujo paleidus įrenginį būtinas PIN kodas"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Iš naujo paleidus įrenginį būtinas slaptažodis"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Norint užtikrinti papildomą saugą būtinas atrakinimo piešinys"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Norint užtikrinti papildomą saugą būtinas PIN kodas"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Norint užtikrinti papildomą saugą būtinas slaptažodis"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Įrenginį užrakino administratorius"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Įrenginys užrakintas neautomatiškai"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Neatpažinta"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Numatytasis"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Debesėlis"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analoginis"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-lv/strings.xml b/packages/SystemUI/res-keyguard/values-lv/strings.xml
index d371a4b..2660a06 100644
--- a/packages/SystemUI/res-keyguard/values-lv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lv/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pēc ierīces restartēšanas ir jāievada atbloķēšanas kombinācija."</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pēc ierīces restartēšanas ir jāievada PIN kods."</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pēc ierīces restartēšanas ir jāievada parole."</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Papildu drošībai ir jāievada atbloķēšanas kombinācija."</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Papildu drošībai ir jāievada PIN kods."</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Papildu drošībai ir jāievada parole."</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administrators bloķēja ierīci."</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Ierīce tika bloķēta manuāli."</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nav atpazīts"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Noklusējums"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Burbuļi"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogais"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-mk/strings.xml b/packages/SystemUI/res-keyguard/values-mk/strings.xml
index ef22564..77e1b50 100644
--- a/packages/SystemUI/res-keyguard/values-mk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mk/strings.xml
@@ -78,9 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Потребна е шема по рестартирање на уредот"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Потребен е PIN-код по рестартирање на уредот"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Потребна е лозинка по рестартирање на уредот"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Потребна е шема за дополнителна безбедност"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Потребен е PIN-код за дополнителна безбедност"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Потребна е лозинка за дополнителна безбедност"</string>
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"За дополнителна безбедност, користете шема"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"За дополнителна безбедност, користете PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"За дополнителна безбедност, користете лозинка"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Уредот е заклучен од администраторот"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Уредот е заклучен рачно"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Непознат"</string>
@@ -90,4 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Стандарден"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Балонче"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Аналоген"</string>
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Отклучете го уредот за да продолжите"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ml/strings.xml b/packages/SystemUI/res-keyguard/values-ml/strings.xml
index 63a542a..e62b435 100644
--- a/packages/SystemUI/res-keyguard/values-ml/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ml/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ഉപകരണം റീസ്റ്റാർട്ടായശേഷം ‌പാറ്റേൺ വരയ്‌ക്കേണ്ടതുണ്ട്"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ഉപകരണം റീസ്റ്റാർട്ടായശേഷം ‌പിൻ നൽകേണ്ടതുണ്ട്"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ഉപകരണം റീസ്റ്റാർട്ടായശേഷം ‌പാസ്‌വേഡ് നൽകേണ്ടതുണ്ട്"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"അധിക സുരക്ഷയ്ക്ക് പാറ്റേൺ ആവശ്യമാണ്"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"അധിക സുരക്ഷയ്ക്ക് പിൻ ആവശ്യമാണ്"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"അധിക സുരക്ഷയ്ക്ക് പാസ്‌വേഡ് ആവശ്യമാണ്"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ഉപകരണം അഡ്‌മിൻ ലോക്കുചെയ്തു"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ഉപകരണം നേരിട്ട് ലോക്കുചെയ്തു"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"തിരിച്ചറിയുന്നില്ല"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ഡിഫോൾട്ട്"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"ബബിൾ"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"അനലോഗ്"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-mn/strings.xml b/packages/SystemUI/res-keyguard/values-mn/strings.xml
index 71c913f..f2cc5ab 100644
--- a/packages/SystemUI/res-keyguard/values-mn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mn/strings.xml
@@ -78,9 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Төхөөрөмжийг дахин эхлүүлсний дараа загвар оруулах шаардлагатай"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Төхөөрөмжийг дахин эхлүүлсний дараа ПИН оруулах шаардлагатай"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Төхөөрөмжийг дахин эхлүүлсний дараа нууц үг оруулах шаардлагатай"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Аюулгүй байдлын үүднээс загвар оруулах шаардлагатай"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Аюулгүй байдлын үүднээс ПИН оруулах шаардлагатай"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Аюулгүй байдлын үүднээс нууц үг оруулах шаардлагатай"</string>
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Нэмэлт аюулгүй байдлын үүднээс оронд нь хээ ашиглана уу"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Нэмэлт аюулгүй байдлын үүднээс оронд нь ПИН ашиглана уу"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Нэмэлт аюулгүй байдлын үүднээс оронд нь нууц үг ашиглана уу"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Админ төхөөрөмжийг түгжсэн"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Төхөөрөмжийг гараар түгжсэн"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Таньж чадсангүй"</string>
@@ -90,4 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Өгөгдмөл"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Бөмбөлөг"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Aналог"</string>
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Үргэлжлүүлэхийн тулд төхөөрөмжийнхөө түгжээг тайлна уу"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-mr/strings.xml b/packages/SystemUI/res-keyguard/values-mr/strings.xml
index 6ac13bd..1454b20 100644
--- a/packages/SystemUI/res-keyguard/values-mr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mr/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"डिव्हाइस रीस्टार्ट झाल्यावर पॅटर्न आवश्यक आहे"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"डिव्हाइस रीस्टार्ट झाल्यावर पिन आवश्यक आहे"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"डिव्हाइस रीस्टार्ट झाल्यावर पासवर्ड आवश्यक आहे"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"अतिरिक्त सुरक्षिततेसाठी पॅटर्न आवश्‍यक आहे"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"अतिरिक्त सुरक्षिततेसाठी पिन आवश्‍यक आहे"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"अतिरिक्त सुरक्षिततेसाठी पासवर्ड आवश्‍यक आहे"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"प्रशासकाद्वारे लॉक केलेले डिव्हाइस"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"डिव्हाइस मॅन्युअली लॉक केले होते"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"ओळखले नाही"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"डीफॉल्ट"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"बबल"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"अ‍ॅनालॉग"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ms/strings.xml b/packages/SystemUI/res-keyguard/values-ms/strings.xml
index 453afc3..a6d1af9 100644
--- a/packages/SystemUI/res-keyguard/values-ms/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ms/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Corak diperlukan setelah peranti dimulakan semula"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN diperlukan setelah peranti dimulakan semula"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Kata laluan diperlukan setelah peranti dimulakan semula"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Corak diperlukan untuk keselamatan tambahan"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN diperlukan untuk keselamatan tambahan"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Kata laluan diperlukan untuk keselamatan tambahan"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Peranti dikunci oleh pentadbir"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Peranti telah dikunci secara manual"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Tidak dikenali"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Lalai"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Gelembung"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-my/strings.xml b/packages/SystemUI/res-keyguard/values-my/strings.xml
index 1cc46b1..5617a11 100644
--- a/packages/SystemUI/res-keyguard/values-my/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-my/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"စက်ပစ္စည်းကို ပိတ်ပြီးပြန်ဖွင့်လိုက်သည့်အခါတွင် ပုံစံ လိုအပ်ပါသည်"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"စက်ပစ္စည်းကို ပိတ်ပြီးပြန်ဖွင့်လိုက်သည့်အခါတွင် ပင်နံပါတ် လိုအပ်ပါသည်"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"စက်ပစ္စည်းကို ပိတ်ပြီးပြန်ဖွင့်လိုက်သည့်အခါတွင် စကားဝှက် လိုအပ်ပါသည်"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ပိုမို၍ လုံခြုံမှု ရှိစေရန် ပုံစံ လိုအပ်ပါသည်"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ပိုမို၍ လုံခြုံမှု ရှိစေရန် ပင်နံပါတ် လိုအပ်ပါသည်"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ပိုမို၍ လုံခြုံမှု ရှိစေရန် စကားဝှက် လိုအပ်ပါသည်"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"စက်ပစ္စည်းကို စီမံခန့်ခွဲသူက လော့ခ်ချထားပါသည်"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"စက်ပစ္စည်းကို ကိုယ်တိုင်ကိုယ်ကျ လော့ခ်ချထားခဲ့သည်"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"မသိ"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"မူလ"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"ပူဖောင်းကွက်"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"ရိုးရိုး"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-nb/strings.xml b/packages/SystemUI/res-keyguard/values-nb/strings.xml
index 5310a730..0ad9e95 100644
--- a/packages/SystemUI/res-keyguard/values-nb/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nb/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Du må tegne mønsteret etter at enheten har startet på nytt"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Du må skrive inn PIN-koden etter at enheten har startet på nytt"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Du må skrive inn passordet etter at enheten har startet på nytt"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Du må tegne mønsteret for ekstra sikkerhet"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Du må skrive inn PIN-koden for ekstra sikkerhet"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Du må skrive inn passordet for ekstra sikkerhet"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Enheten er låst av administratoren"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Enheten ble låst manuelt"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Ikke gjenkjent"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Standard"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Boble"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ne/strings.xml b/packages/SystemUI/res-keyguard/values-ne/strings.xml
index 534164b..196b74a 100644
--- a/packages/SystemUI/res-keyguard/values-ne/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ne/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"यन्त्र पुनः सुरु भएपछि ढाँचा आवश्यक पर्दछ"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"यन्त्र पुनः सुरु भएपछि PIN आवश्यक पर्दछ"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"यन्त्र पुनः सुरु भएपछि पासवर्ड आवश्यक पर्दछ"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"अतिरिक्त सुरक्षाको लागि ढाँचा आवश्यक छ"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"अतिरिक्त सुरक्षाको लागि PIN आवश्यक छ"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"अतिरिक्त सुरक्षाको लागि पासवर्ड आवश्यक छ"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"प्रशासकले यन्त्रलाई लक गर्नुभएको छ"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"यन्त्रलाई म्यानुअल तरिकाले लक गरिएको थियो"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"पहिचान भएन"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"डिफल्ट"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"बबल"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"एनालग"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml
index 08e226d4..747b3bb 100644
--- a/packages/SystemUI/res-keyguard/values-nl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Patroon vereist nadat het apparaat opnieuw is opgestart"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pincode vereist nadat het apparaat opnieuw is opgestart"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Wachtwoord vereist nadat het apparaat opnieuw is opgestart"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Patroon vereist voor extra beveiliging"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Pincode vereist voor extra beveiliging"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Wachtwoord vereist voor extra beveiliging"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Apparaat vergrendeld door beheerder"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Apparaat is handmatig vergrendeld"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Niet herkend"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Standaard"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bel"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analoog"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-or/strings.xml b/packages/SystemUI/res-keyguard/values-or/strings.xml
index 3cdd264..75f7a89 100644
--- a/packages/SystemUI/res-keyguard/values-or/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-or/strings.xml
@@ -78,9 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ଡିଭାଇସ୍‍ ରିଷ୍ଟାର୍ଟ ହେବା ପରେ ପାଟର୍ନ ଆବଶ୍ୟକ ଅଟେ"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ଡିଭାଇସ୍‍ ରିଷ୍ଟାର୍ଟ ହେବାପରେ ପାସ୍‌ୱର୍ଡ ଆବଶ୍ୟକ"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ଡିଭାଇସ୍‍ ରିଷ୍ଟାର୍ଟ ହେବା ପରେ ପାସୱର୍ଡ ଆବଶ୍ୟକ ଅଟେ"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ ପାଟର୍ନ ଆବଶ୍ୟକ"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ PIN ଆବଶ୍ୟକ ଅଟେ"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ ପାସ୍‌ୱର୍ଡ ଆବଶ୍ୟକ"</string>
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ, ଏହା ପରିବର୍ତ୍ତେ ପାଟର୍ନ ବ୍ୟବହାର କରନ୍ତୁ"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ, ଏହା ପରିବର୍ତ୍ତେ PIN ବ୍ୟବହାର କରନ୍ତୁ"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ, ଏହା ପରିବର୍ତ୍ତେ ପାସୱାର୍ଡ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ଡିଭାଇସ୍‍ ଆଡମିନଙ୍କ ଦ୍ୱାରା ଲକ୍‍ କରାଯାଇଛି"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ଡିଭାଇସ୍‍ ମାନୁଆଲ ଭାବେ ଲକ୍‍ କରାଗଲା"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"ଚିହ୍ନଟ ହେଲାନାହିଁ"</string>
@@ -90,4 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ଡିଫଲ୍ଟ"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"ବବଲ୍"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"ଆନାଲଗ୍"</string>
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ଜାରି ରଖିବା ପାଇଁ ଆପଣଙ୍କ ଡିଭାଇସକୁ ଅନଲକ କରନ୍ତୁ"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-pa/strings.xml b/packages/SystemUI/res-keyguard/values-pa/strings.xml
index 409f727..bf1a359a 100644
--- a/packages/SystemUI/res-keyguard/values-pa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pa/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ਡੀਵਾਈਸ ਦੇ ਮੁੜ-ਚਾਲੂ ਹੋਣ \'ਤੇ ਪੈਟਰਨ ਦੀ ਲੋੜ ਹੈ"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ਡੀਵਾਈਸ ਦੇ ਮੁੜ-ਚਾਲੂ ਹੋਣ \'ਤੇ ਪਿੰਨ ਦੀ ਲੋੜ ਹੈ"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ਡੀਵਾਈਸ ਦੇ ਮੁੜ-ਚਾਲੂ ਹੋਣ \'ਤੇ ਪਾਸਵਰਡ ਦੀ ਲੋੜ ਹੈ"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ਵਧੀਕ ਸੁਰੱਖਿਆ ਲਈ ਪੈਟਰਨ ਦੀ ਲੋੜ ਹੈ"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ਵਧੀਕ ਸੁਰੱਖਿਆ ਲਈ ਪਿੰਨ ਦੀ ਲੋੜ ਹੈ"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ਵਧੀਕ ਸੁਰੱਖਿਆ ਲਈ ਪਾਸਵਰਡ ਦੀ ਲੋੜ ਹੈ"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਡੀਵਾਈਸ ਨੂੰ ਲਾਕ ਕੀਤਾ ਗਿਆ"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ਡੀਵਾਈਸ ਨੂੰ ਹੱਥੀਂ ਲਾਕ ਕੀਤਾ ਗਿਆ"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"ਪਛਾਣ ਨਹੀਂ ਹੋਈ"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ਪੂਰਵ-ਨਿਰਧਾਰਿਤ"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"ਬੁਲਬੁਲਾ"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"ਐਨਾਲੌਗ"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-pl/strings.xml b/packages/SystemUI/res-keyguard/values-pl/strings.xml
index 52bc982..c49149b 100644
--- a/packages/SystemUI/res-keyguard/values-pl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pl/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po ponownym uruchomieniu urządzenia wymagany jest wzór"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po ponownym uruchomieniu urządzenia wymagany jest kod PIN"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po ponownym uruchomieniu urządzenia wymagane jest hasło"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Dla większego bezpieczeństwa musisz narysować wzór"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Dla większego bezpieczeństwa musisz podać kod PIN"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Dla większego bezpieczeństwa musisz podać hasło"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Urządzenie zablokowane przez administratora"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Urządzenie zostało zablokowane ręcznie"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nie rozpoznano"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Domyślna"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bąbelkowy"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogowy"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
index b934826..3d60e8c 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
@@ -78,9 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"O padrão é exigido após a reinicialização do dispositivo"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"O PIN é exigido após a reinicialização do dispositivo"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"A senha é exigida após a reinicialização do dispositivo"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"O padrão é necessário para aumentar a segurança"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"O PIN é necessário para aumentar a segurança"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"A senha é necessária para aumentar a segurança"</string>
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Para ter mais segurança, use o padrão"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Para ter mais segurança, use o PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Para ter mais segurança, use a senha"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado pelo administrador"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"O dispositivo foi bloqueado manualmente"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Não reconhecido"</string>
@@ -90,4 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Padrão"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bolha"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Desbloqueie o dispositivo para continuar"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
index a67bfb0..0a94349 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
@@ -78,9 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"É necessário um padrão após reiniciar o dispositivo"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"É necessário um PIN após reiniciar o dispositivo"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"É necessária uma palavra-passe após reiniciar o dispositivo"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Para segurança adicional, é necessário um padrão"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Para segurança adicional, é necessário um PIN"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Para segurança adicional, é necessária uma palavra-passe"</string>
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Para uma segurança adicional, use antes o padrão"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Para uma segurança adicional, use antes o PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Para uma segurança adicional, use antes a palavra-passe"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado pelo gestor"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"O dispositivo foi bloqueado manualmente"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Não reconhecido."</string>
@@ -90,4 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Predefinido"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Balão"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Desbloqueie o dispositivo para continuar"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-pt/strings.xml b/packages/SystemUI/res-keyguard/values-pt/strings.xml
index b934826..3d60e8c 100644
--- a/packages/SystemUI/res-keyguard/values-pt/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt/strings.xml
@@ -78,9 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"O padrão é exigido após a reinicialização do dispositivo"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"O PIN é exigido após a reinicialização do dispositivo"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"A senha é exigida após a reinicialização do dispositivo"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"O padrão é necessário para aumentar a segurança"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"O PIN é necessário para aumentar a segurança"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"A senha é necessária para aumentar a segurança"</string>
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Para ter mais segurança, use o padrão"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Para ter mais segurança, use o PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Para ter mais segurança, use a senha"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado pelo administrador"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"O dispositivo foi bloqueado manualmente"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Não reconhecido"</string>
@@ -90,4 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Padrão"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bolha"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Desbloqueie o dispositivo para continuar"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ro/strings.xml b/packages/SystemUI/res-keyguard/values-ro/strings.xml
index 5ee67d91..547224e 100644
--- a/packages/SystemUI/res-keyguard/values-ro/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ro/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Modelul este necesar după repornirea dispozitivului"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Codul PIN este necesar după repornirea dispozitivului"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Parola este necesară după repornirea dispozitivului"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Modelul este necesar pentru securitate suplimentară"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Codul PIN este necesar pentru securitate suplimentară"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Parola este necesară pentru securitate suplimentară"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispozitiv blocat de administrator"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Dispozitivul a fost blocat manual"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nu este recunoscut"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Prestabilit"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Balon"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogic"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ru/strings.xml b/packages/SystemUI/res-keyguard/values-ru/strings.xml
index 2b8f8d6..f1945ad 100644
--- a/packages/SystemUI/res-keyguard/values-ru/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ru/strings.xml
@@ -78,9 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"После перезагрузки устройства необходимо ввести графический ключ"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"После перезагрузки устройства необходимо ввести PIN-код"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"После перезагрузки устройства необходимо ввести пароль"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"В качестве дополнительной меры безопасности введите графический ключ"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"В качестве дополнительной меры безопасности введите PIN-код"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"В качестве дополнительной меры безопасности введите пароль"</string>
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"В целях дополнительной безопасности используйте графический ключ"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"В целях дополнительной безопасности используйте PIN-код"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"В целях дополнительной безопасности используйте пароль"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Устройство заблокировано администратором"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Устройство было заблокировано вручную"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Не распознано"</string>
@@ -90,4 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"По умолчанию"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Пузырь"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Стрелки"</string>
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Чтобы продолжить, разблокируйте устройство"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-si/strings.xml b/packages/SystemUI/res-keyguard/values-si/strings.xml
index 4e911de..e5862c3 100644
--- a/packages/SystemUI/res-keyguard/values-si/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-si/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"උපාංගය නැවත ආරම්භ වූ පසු රටාව අවශ්‍යයි"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"උපාංගය නැවත ආරම්භ වූ පසු PIN අංකය අවශ්‍යයි"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"උපාංගය නැවත ආරම්භ වූ පසු මුරපදය අවශ්‍යයි"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"අමතර ආරක්ෂාව සඳහා රටාව අවශ්‍යයි"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"අමතර ආරක්ෂාව සඳහා PIN අංකය අවශ්‍යයි"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"අමතර ආරක්ෂාව සඳහා මුරපදය අවශ්‍යයි"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ඔබගේ පරිපාලක විසින් උපාංගය අගුළු දමා ඇත"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"උපාංගය හස්තීයව අගුලු දමන ලදී"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"හඳුනා නොගන්නා ලදී"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"පෙරනිමි"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"බුබුළ"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"ප්‍රතිසමය"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-sk/strings.xml b/packages/SystemUI/res-keyguard/values-sk/strings.xml
index f2d68e3..efe4ec8 100644
--- a/packages/SystemUI/res-keyguard/values-sk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sk/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po reštartovaní zariadenia musíte zadať bezpečnostný vzor"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po reštartovaní zariadenia musíte zadať kód PIN"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po reštartovaní zariadenia musíte zadať heslo"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Na ďalšie zabezpečenie musíte zadať bezpečnostný vzor"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Na ďalšie zabezpečenie musíte zadať kód PIN"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Na ďalšie zabezpečenie musíte zadať heslo"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Zariadenie zamkol správca"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Zariadenie bolo uzamknuté ručne"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nerozpoznané"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Predvolený"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bublina"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analógový"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-sl/strings.xml b/packages/SystemUI/res-keyguard/values-sl/strings.xml
index 772308f..52726c2 100644
--- a/packages/SystemUI/res-keyguard/values-sl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sl/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po vnovičnem zagonu naprave je treba vnesti vzorec"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po vnovičnem zagonu naprave je treba vnesti kodo PIN"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po vnovičnem zagonu naprave je treba vnesti geslo"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Zaradi dodatne varnosti morate vnesti vzorec"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Zaradi dodatne varnosti morate vnesti kodo PIN"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Zaradi dodatne varnosti morate vnesti geslo"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Napravo je zaklenil skrbnik"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Naprava je bila ročno zaklenjena"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Ni prepoznano"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Privzeto"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Mehurček"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogno"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-sq/strings.xml b/packages/SystemUI/res-keyguard/values-sq/strings.xml
index c758462..a0a5594 100644
--- a/packages/SystemUI/res-keyguard/values-sq/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sq/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Kërkohet motivi pas rinisjes së pajisjes"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Kërkohet kodi PIN pas rinisjes së pajisjes"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Kërkohet fjalëkalimi pas rinisjes së pajisjes"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Kërkohet motivi për më shumë siguri"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Kërkohet kodi PIN për më shumë siguri"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Kërkohet fjalëkalimi për më shumë siguri"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Pajisja është e kyçur nga administratori"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Pajisja është kyçur manualisht"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nuk njihet"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"E parazgjedhur"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Flluskë"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analoge"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-sr/strings.xml b/packages/SystemUI/res-keyguard/values-sr/strings.xml
index e6fe853..e634fdcb5 100644
--- a/packages/SystemUI/res-keyguard/values-sr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sr/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Треба да унесете шаблон када се уређај поново покрене"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Треба да унесете PIN када се уређај поново покрене"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Треба да унесете лозинку када се уређај поново покрене"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Треба да унесете шаблон ради додатне безбедности"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Треба да унесете PIN ради додатне безбедности"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Треба да унесете лозинку ради додатне безбедности"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Администратор је закључао уређај"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Уређај је ручно закључан"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Није препознат"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Подразумевани"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Мехурићи"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Аналогни"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-sv/strings.xml b/packages/SystemUI/res-keyguard/values-sv/strings.xml
index fa241d9..fc9beb1 100644
--- a/packages/SystemUI/res-keyguard/values-sv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sv/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Du måste rita mönster när du har startat om enheten"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Du måste ange pinkod när du har startat om enheten"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Du måste ange lösenord när du har startat om enheten"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Du måste rita mönster för ytterligare säkerhet"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Du måste ange pinkod för ytterligare säkerhet"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Du måste ange lösenord för ytterligare säkerhet"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administratören har låst enheten"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Enheten har låsts manuellt"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Identifierades inte"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Standard"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bubbla"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-sw/strings.xml b/packages/SystemUI/res-keyguard/values-sw/strings.xml
index 791bceb..bcab24b 100644
--- a/packages/SystemUI/res-keyguard/values-sw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sw/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Unafaa kuchora mchoro baada ya kuwasha kifaa upya"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Unafaa kuweka PIN baada ya kuwasha kifaa upya"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Unafaa kuweka nenosiri baada ya kuwasha kifaa upya"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Mchoro unahitajika ili kuongeza usalama"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN inahitajika ili kuongeza usalama"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Nenosiri linahitajika ili kuongeza usalama."</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Msimamizi amefunga kifaa"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Umefunga kifaa mwenyewe"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Haitambuliwi"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Chaguomsingi"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Kiputo"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogi"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ta/strings.xml b/packages/SystemUI/res-keyguard/values-ta/strings.xml
index 271657d..88d5760 100644
--- a/packages/SystemUI/res-keyguard/values-ta/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ta/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"சாதனத்தை மீண்டும் தொடங்கியதும், பேட்டர்னை வரைய வேண்டும்"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"சாதனத்தை மீண்டும் தொடங்கியதும், பின்னை உள்ளிட வேண்டும்"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"சாதனத்தை மீண்டும் தொடங்கியதும், கடவுச்சொல்லை உள்ளிட வேண்டும்"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"கூடுதல் பாதுகாப்பிற்கு, பேட்டர்னை வரைய வேண்டும்"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"கூடுதல் பாதுகாப்பிற்கு, பின்னை உள்ளிட வேண்டும்"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"கூடுதல் பாதுகாப்பிற்கு, கடவுச்சொல்லை உள்ளிட வேண்டும்"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"நிர்வாகி சாதனத்தைப் பூட்டியுள்ளார்"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"பயனர் சாதனத்தைப் பூட்டியுள்ளார்"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"அடையாளங்காணபடவில்லை"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"இயல்பு"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"பபிள்"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"அனலாக்"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-te/strings.xml b/packages/SystemUI/res-keyguard/values-te/strings.xml
index f62e667..3a0111a 100644
--- a/packages/SystemUI/res-keyguard/values-te/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-te/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"పరికరాన్ని పునఃప్రారంభించిన తర్వాత నమూనాను గీయాలి"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"డివైజ్‌ను పునఃప్రారంభించిన తర్వాత పిన్ నమోదు చేయాలి"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"పరికరాన్ని పునఃప్రారంభించిన తర్వాత పాస్‌వర్డ్‌ను నమోదు చేయాలి"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"అదనపు సెక్యూరిటీ కోసం ఆకృతి అవసరం"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"అదనపు సెక్యూరిటీ కోసం పిన్ ఎంటర్ చేయాలి"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"అదనపు సెక్యూరిటీ కోసం పాస్‌వర్డ్‌ను ఎంటర్ చేయాలి"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"పరికరం నిర్వాహకుల ద్వారా లాక్ చేయబడింది"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"పరికరం మాన్యువల్‌గా లాక్ చేయబడింది"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"గుర్తించలేదు"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ఆటోమేటిక్"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"బబుల్"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"ఎనలాగ్"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-th/strings.xml b/packages/SystemUI/res-keyguard/values-th/strings.xml
index 62a83bc..14a65a07 100644
--- a/packages/SystemUI/res-keyguard/values-th/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-th/strings.xml
@@ -78,9 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ต้องวาดรูปแบบหลังจากอุปกรณ์รีสตาร์ท"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ต้องระบุ PIN หลังจากอุปกรณ์รีสตาร์ท"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ต้องป้อนรหัสผ่านหลังจากอุปกรณ์รีสตาร์ท"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ต้องวาดรูปแบบเพื่อความปลอดภัยเพิ่มเติม"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ต้องระบุ PIN เพื่อความปลอดภัยเพิ่มเติม"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ต้องป้อนรหัสผ่านเพื่อความปลอดภัยเพิ่มเติม"</string>
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ใช้รูปแบบแทนเพื่อเพิ่มความปลอดภัย"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ใช้ PIN แทนเพื่อเพิ่มความปลอดภัย"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ใช้รหัสผ่านแทนเพื่อเพิ่มความปลอดภัย"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ผู้ดูแลระบบล็อกอุปกรณ์"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"มีการล็อกอุปกรณ์ด้วยตัวเอง"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"ไม่รู้จัก"</string>
@@ -90,4 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ค่าเริ่มต้น"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"บับเบิล"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"แอนะล็อก"</string>
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ปลดล็อกอุปกรณ์ของคุณเพื่อดำเนินการต่อ"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-tl/strings.xml b/packages/SystemUI/res-keyguard/values-tl/strings.xml
index 524ea47..7936058 100644
--- a/packages/SystemUI/res-keyguard/values-tl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tl/strings.xml
@@ -78,9 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Kailangan ng pattern pagkatapos mag-restart ng device"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Kailangan ng PIN pagkatapos mag-restart ng device"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Kailangan ng password pagkatapos mag-restart ng device"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Kinakailangan ang pattern para sa karagdagang seguridad"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Kinakailangan ang PIN para sa karagdagang seguridad"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Kinakailangan ang password para sa karagdagang seguridad"</string>
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Para sa karagdagang seguridad, gumamit na lang ng pattern"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Para sa karagdagang seguridad, gumamit na lang ng PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Para sa karagdagang seguridad, gumamit na lang ng password"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Na-lock ng admin ang device"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Manual na na-lock ang device"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Hindi nakilala"</string>
@@ -90,4 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"I-unlock ang iyong device para magpatuloy"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-tr/strings.xml b/packages/SystemUI/res-keyguard/values-tr/strings.xml
index 54aaae3..e520762 100644
--- a/packages/SystemUI/res-keyguard/values-tr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tr/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Cihaz yeniden başladıktan sonra desen gerekir"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Cihaz yeniden başladıktan sonra PIN gerekir"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Cihaz yeniden başladıktan sonra şifre gerekir"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Ek güvenlik için desen gerekir"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Ek güvenlik için PIN gerekir"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Ek güvenlik için şifre gerekir"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Cihaz, yönetici tarafından kilitlendi"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Cihazın manuel olarak kilitlendi"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Tanınmadı"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Varsayılan"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Baloncuk"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-uk/strings.xml b/packages/SystemUI/res-keyguard/values-uk/strings.xml
index 6144c1c..613181d 100644
--- a/packages/SystemUI/res-keyguard/values-uk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uk/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Після перезавантаження пристрою потрібно ввести ключ"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Після перезавантаження пристрою потрібно ввести PIN-код"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Після перезавантаження пристрою потрібно ввести пароль"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Для додаткового захисту потрібно ввести ключ"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Для додаткового захисту потрібно ввести PIN-код"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Для додаткового захисту потрібно ввести пароль"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Адміністратор заблокував пристрій"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Пристрій заблоковано вручну"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Не розпізнано"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"За умовчанням"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Бульбашковий"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Аналоговий"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ur/strings.xml b/packages/SystemUI/res-keyguard/values-ur/strings.xml
index 4e77841..a122f85 100644
--- a/packages/SystemUI/res-keyguard/values-ur/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ur/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"آلہ دوبارہ چالو ہونے کے بعد پیٹرن درکار ہوتا ہے"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"‏آلہ دوبارہ چالو ہونے کے بعد PIN درکار ہوتا ہے"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"آلہ دوبارہ چالو ہونے کے بعد پاس ورڈ درکار ہوتا ہے"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"اضافی سیکیورٹی کیلئے پیٹرن درکار ہے"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"‏اضافی سیکیورٹی کیلئے PIN درکار ہے"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"اضافی سیکیورٹی کیلئے پاس ورڈ درکار ہے"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"آلہ منتظم کی جانب سے مقفل ہے"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"آلہ کو دستی طور پر مقفل کیا گیا تھا"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"تسلیم شدہ نہیں ہے"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ڈیفالٹ"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"بلبلہ"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"اینالاگ"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-uz/strings.xml b/packages/SystemUI/res-keyguard/values-uz/strings.xml
index afaf746..2cc9724 100644
--- a/packages/SystemUI/res-keyguard/values-uz/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uz/strings.xml
@@ -78,9 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Qurilma qayta ishga tushganidan keyin grafik kalitni kiritish zarur"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Qurilma qayta ishga tushganidan keyin PIN kodni kiritish zarur"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Qurilma qayta ishga tushganidan keyin parolni kiritish zarur"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Qo‘shimcha xavfsizlik chorasi sifatida grafik kalit talab qilinadi"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Qo‘shimcha xavfsizlik chorasi sifatida PIN kod talab qilinadi"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Qo‘shimcha xavfsizlik chorasi sifatida parol talab qilinadi"</string>
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Qoʻshimcha xavfsizlik maqsadida oʻrniga grafik kalitdan foydalaning"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Qoʻshimcha xavfsizlik maqsadida oʻrniga PIN koddan foydalaning"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Qoʻshimcha xavfsizlik maqsadida oʻrniga paroldan foydalaning"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Qurilma administrator tomonidan bloklangan"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Qurilma qo‘lda qulflangan"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Aniqlanmadi"</string>
@@ -90,4 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Odatiy"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Pufaklar"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Davom etish uchun qurilmangizni qulfdan chiqaring"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-vi/strings.xml b/packages/SystemUI/res-keyguard/values-vi/strings.xml
index 1d6cfa8..e7c9295 100644
--- a/packages/SystemUI/res-keyguard/values-vi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-vi/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Yêu cầu hình mở khóa sau khi thiết bị khởi động lại"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Yêu cầu mã PIN sau khi thiết bị khởi động lại"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Yêu cầu mật khẩu sau khi thiết bị khởi động lại"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Yêu cầu hình mở khóa để bảo mật thêm"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Yêu cầu mã PIN để bảo mật thêm"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Yêu cầu mật khẩu để bảo mật thêm"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Thiết bị đã bị quản trị viên khóa"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Thiết bị đã bị khóa theo cách thủ công"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Không nhận dạng được"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Mặc định"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bong bóng"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Đồng hồ kim"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
index 8c8507e..d37d645 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"重启设备后需要绘制解锁图案"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"重启设备后需要输入 PIN 码"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"重启设备后需要输入密码"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"需要绘制解锁图案以进一步确保安全"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"需要输入 PIN 码以进一步确保安全"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"需要输入密码以进一步确保安全"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"管理员已锁定设备"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"此设备已手动锁定"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"无法识别"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"默认"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"泡泡"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"指针"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
index c331a92..9dbb8f2 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"裝置重新啟動後,必須畫出上鎖圖案才能使用"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"裝置重新啟動後,必須輸入 PIN 碼才能使用"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"裝置重新啟動後,必須輸入密碼才能使用"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"請務必畫出上鎖圖案,以進一步確保安全"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"請務必輸入 PIN 碼,以進一步確保安全"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"請務必輸入密碼,以進一步確保安全"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"裝置已由管理員鎖定"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"使用者已手動將裝置上鎖"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"未能識別"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"預設"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"泡泡"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"指針"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
index 1e1bec3..ebb88e1 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"裝置重新啟動後需要畫出解鎖圖案"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"裝置重新啟動後需要輸入 PIN 碼"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"裝置重新啟動後需要輸入密碼"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"請畫出解鎖圖案,以進一步確保資訊安全"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"請輸入 PIN 碼,以進一步確保資訊安全"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"請輸入密碼,以進一步確保資訊安全"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"管理員已鎖定裝置"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"裝置已手動鎖定"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"無法識別"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"預設"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"泡泡"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"類比"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-zu/strings.xml b/packages/SystemUI/res-keyguard/values-zu/strings.xml
index c8f78ea..57e56f7 100644
--- a/packages/SystemUI/res-keyguard/values-zu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zu/strings.xml
@@ -78,9 +78,12 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Iphethini iyadingeka ngemuva kokuqala kabusha kwedivayisi"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Iphinikhodi iyadingeka ngemuva kokuqala kabusha kwedivayisi"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Iphasiwedi iyadingeka ngemuva kokuqala kabusha kwedivayisi"</string>
-    <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Kudingeka iphethini  ngokuvikeleka okungeziwe"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Kudingeka iphinikhodi ngokuvikeleka okungeziwe"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Iphasiwedi idingelwa ukuvikela okungeziwe"</string>
+    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+    <skip />
+    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+    <skip />
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Idivayisi ikhiywe ngumlawuli"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Idivayisi ikhiywe ngokwenza"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Akwaziwa"</string>
@@ -90,4 +93,6 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Okuzenzekelayo"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Ibhamuza"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"I-Analog"</string>
+    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values/config.xml b/packages/SystemUI/res-keyguard/values/config.xml
index b1d3375..a25ab51 100644
--- a/packages/SystemUI/res-keyguard/values/config.xml
+++ b/packages/SystemUI/res-keyguard/values/config.xml
@@ -28,11 +28,6 @@
     <!-- Will display the bouncer on one side of the display, and the current user icon and
          user switcher on the other side -->
     <bool name="config_enableBouncerUserSwitcher">false</bool>
-    <!-- Whether to show the face scanning animation on devices with face auth supported.
-         The face scanning animation renders in a SW layer in ScreenDecorations.
-         Enabling this will also render the camera protection in the SW layer
-         (instead of HW, if relevant)."=-->
-    <bool name="config_enableFaceScanningAnimation">true</bool>
     <!-- Time to be considered a consecutive fingerprint failure in ms -->
     <integer name="fp_consecutive_failure_time_ms">3500</integer>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 46f6ab2..0a55cf7 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -119,6 +119,7 @@
     <dimen name="bouncer_user_switcher_width">248dp</dimen>
     <dimen name="bouncer_user_switcher_popup_header_height">12dp</dimen>
     <dimen name="bouncer_user_switcher_popup_divider_height">4dp</dimen>
+    <dimen name="bouncer_user_switcher_popup_items_divider_height">2dp</dimen>
     <dimen name="bouncer_user_switcher_item_padding_vertical">10dp</dimen>
     <dimen name="bouncer_user_switcher_item_padding_horizontal">12dp</dimen>
     <dimen name="bouncer_user_switcher_header_padding_end">44dp</dimen>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 8135aaa..a129fb6 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -201,13 +201,13 @@
     <string name="kg_prompt_reason_restart_password">Password required after device restarts</string>
 
     <!-- An explanation text that the pattern needs to be solved since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
-    <string name="kg_prompt_reason_timeout_pattern">Pattern required for additional security</string>
+    <string name="kg_prompt_reason_timeout_pattern">For additional security, use pattern instead</string>
 
     <!-- An explanation text that the pin needs to be entered since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
-    <string name="kg_prompt_reason_timeout_pin">PIN required for additional security</string>
+    <string name="kg_prompt_reason_timeout_pin">For additional security, use PIN instead</string>
 
     <!-- An explanation text that the password needs to be entered since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
-    <string name="kg_prompt_reason_timeout_password">Password required for additional security</string>
+    <string name="kg_prompt_reason_timeout_password">For additional security, use password instead</string>
 
     <!-- An explanation text that the credential needs to be entered because a device admin has
     locked the device. [CHAR LIMIT=80] -->
diff --git a/packages/SystemUI/res/drawable-hdpi/textfield_default_filled.9.png b/packages/SystemUI/res/drawable-hdpi/textfield_default_filled.9.png
new file mode 100644
index 0000000..3dd997f
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/textfield_default_filled.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/textfield_default_filled.9.png b/packages/SystemUI/res/drawable-mdpi/textfield_default_filled.9.png
new file mode 100644
index 0000000..80aba01
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/textfield_default_filled.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/textfield_default_filled.9.png b/packages/SystemUI/res/drawable-xhdpi/textfield_default_filled.9.png
new file mode 100644
index 0000000..b3f89ed
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/textfield_default_filled.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/textfield_default_filled.9.png b/packages/SystemUI/res/drawable-xxhdpi/textfield_default_filled.9.png
new file mode 100644
index 0000000..efa2cb9
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/textfield_default_filled.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable/edit_text_filled.xml b/packages/SystemUI/res/drawable/edit_text_filled.xml
new file mode 100644
index 0000000..cca34d4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/edit_text_filled.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+       android:insetLeft="4dp"
+       android:insetRight="4dp"
+       android:insetTop="10dp"
+       android:insetBottom="10dp">
+    <selector>
+        <item android:state_enabled="false">
+            <nine-patch android:src="@drawable/textfield_default_filled"
+                android:tint="?android:attr/colorControlNormal" />
+        </item>
+        <item android:state_pressed="false" android:state_focused="false">
+            <nine-patch android:src="@drawable/textfield_default_filled"
+                android:tint="?android:attr/colorControlNormal" />
+        </item>
+        <item>
+            <nine-patch android:src="@drawable/textfield_default_filled"
+                android:tint="?android:attr/colorControlActivated" />
+        </item>
+    </selector>
+</inset>
diff --git a/packages/SystemUI/res-keyguard/drawable/media_squiggly_progress.xml b/packages/SystemUI/res/drawable/media_squiggly_progress.xml
similarity index 91%
rename from packages/SystemUI/res-keyguard/drawable/media_squiggly_progress.xml
rename to packages/SystemUI/res/drawable/media_squiggly_progress.xml
index 9e61236..9cd3f62 100644
--- a/packages/SystemUI/res-keyguard/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.SquigglyProgress />
\ No newline at end of file
+<com.android.systemui.media.controls.ui.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 6ed3a0ae..217656da 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.IlluminationDrawable
+<com.android.systemui.media.controls.ui.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 b2647c1..849349a 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.LightSourceDrawable
+<com.android.systemui.media.controls.ui.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-land/auth_credential_password_view.xml b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
index da76c8d..3bcc37a 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
@@ -16,46 +16,74 @@
 
 <com.android.systemui.biometrics.AuthCredentialPasswordView
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:gravity="center_horizontal"
-    android:elevation="@dimen/biometric_dialog_elevation">
+    android:orientation="horizontal"
+    android:elevation="@dimen/biometric_dialog_elevation"
+    android:theme="?app:attr/lockPinPasswordStyle">
 
-    <TextView
-        android:id="@+id/title"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        style="@style/TextAppearance.AuthCredential.Title"/>
+    <RelativeLayout
+        android:id="@+id/auth_credential_header"
+        style="?headerStyle"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent">
 
-    <TextView
-        android:id="@+id/subtitle"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        style="@style/TextAppearance.AuthCredential.Subtitle"/>
+        <ImageView
+            android:id="@+id/icon"
+            style="?headerIconStyle"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentTop="true"
+            android:contentDescription="@null"/>
 
-    <TextView
-        android:id="@+id/description"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        style="@style/TextAppearance.AuthCredential.Description"/>
+        <TextView
+            android:id="@+id/title"
+            style="?titleTextAppearance"
+            android:layout_below="@id/icon"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
 
-    <ImeAwareEditText
-        android:id="@+id/lockPassword"
-        android:layout_width="208dp"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_horizontal"
-        android:minHeight="48dp"
-        android:gravity="center"
-        android:inputType="textPassword"
-        android:maxLength="500"
-        android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
-        style="@style/TextAppearance.AuthCredential.PasswordEntry"/>
+        <TextView
+            android:id="@+id/subtitle"
+            style="?subTitleTextAppearance"
+            android:layout_below="@id/title"
+            android:layout_alignParentLeft="true"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
 
-    <TextView
-        android:id="@+id/error"
-        android:layout_width="match_parent"
+        <TextView
+            android:id="@+id/description"
+            style="?descriptionTextAppearance"
+            android:layout_below="@id/subtitle"
+            android:layout_alignParentLeft="true"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+    </RelativeLayout>
+
+    <LinearLayout
+        android:id="@+id/auth_credential_input"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        style="@style/TextAppearance.AuthCredential.Error"/>
+        android:orientation="vertical">
+
+        <ImeAwareEditText
+            android:id="@+id/lockPassword"
+            style="?passwordTextAppearance"
+            android:layout_width="208dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
+            android:inputType="textPassword"
+            android:minHeight="48dp" />
+
+        <TextView
+            android:id="@+id/error"
+            style="?errorTextAppearance"
+            android:layout_gravity="center"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+    </LinearLayout>
 
 </com.android.systemui.biometrics.AuthCredentialPasswordView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
index 19a85fe..a3dd334 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
@@ -16,91 +16,71 @@
 
 <com.android.systemui.biometrics.AuthCredentialPatternView
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="horizontal"
-    android:elevation="@dimen/biometric_dialog_elevation">
+    android:elevation="@dimen/biometric_dialog_elevation"
+    android:theme="?app:attr/lockPatternStyle">
 
-    <LinearLayout
+    <RelativeLayout
+        android:id="@+id/auth_credential_header"
+        style="?headerStyle"
         android:layout_width="0dp"
         android:layout_height="match_parent"
-        android:layout_weight="1"
-        android:gravity="center"
-        android:orientation="vertical">
-
-        <Space
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:layout_weight="1"/>
+        android:layout_weight="1">
 
         <ImageView
             android:id="@+id/icon"
+            style="?headerIconStyle"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentTop="true"
+            android:contentDescription="@null"/>
+
+        <TextView
+            android:id="@+id/title"
+            style="?titleTextAppearance"
+            android:layout_below="@id/icon"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"/>
 
         <TextView
-            android:id="@+id/title"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            style="@style/TextAppearance.AuthCredential.Title"/>
-
-        <TextView
             android:id="@+id/subtitle"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            style="@style/TextAppearance.AuthCredential.Subtitle"/>
+            style="?subTitleTextAppearance"
+            android:layout_below="@id/title"
+            android:layout_alignParentLeft="true"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
 
         <TextView
             android:id="@+id/description"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            style="@style/TextAppearance.AuthCredential.Description"/>
+            style="?descriptionTextAppearance"
+            android:layout_below="@id/subtitle"
+            android:layout_alignParentLeft="true"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
 
-        <Space
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:layout_weight="1"/>
+    </RelativeLayout>
+
+    <FrameLayout
+        android:layout_weight="1"
+        style="?containerStyle"
+        android:layout_width="0dp"
+        android:layout_height="match_parent">
+
+        <com.android.internal.widget.LockPatternView
+            android:id="@+id/lockPattern"
+            android:layout_gravity="center"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
 
         <TextView
             android:id="@+id/error"
+            style="?errorTextAppearance"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            style="@style/TextAppearance.AuthCredential.Error"/>
+            android:layout_gravity="center_horizontal|bottom"/>
 
-        <Space
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:layout_weight="1"/>
-
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        android:orientation="vertical"
-        android:gravity="center"
-        android:paddingLeft="0dp"
-        android:paddingRight="0dp"
-        android:paddingTop="0dp"
-        android:paddingBottom="16dp"
-        android:clipToPadding="false">
-
-        <FrameLayout
-            android:layout_width="wrap_content"
-            android:layout_height="0dp"
-            android:layout_weight="1"
-            style="@style/LockPatternContainerStyle">
-
-            <com.android.internal.widget.LockPatternView
-                android:id="@+id/lockPattern"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_gravity="center"
-                style="@style/LockPatternStyleBiometricPrompt"/>
-
-        </FrameLayout>
-
-    </LinearLayout>
+    </FrameLayout>
 
 </com.android.systemui.biometrics.AuthCredentialPatternView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml
index 0ff1db2..774b335f 100644
--- a/packages/SystemUI/res/layout/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml
@@ -16,74 +16,71 @@
 
 <com.android.systemui.biometrics.AuthCredentialPasswordView
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:elevation="@dimen/biometric_dialog_elevation"
-    android:orientation="vertical">
+    android:orientation="vertical"
+    android:theme="?app:attr/lockPinPasswordStyle">
 
     <RelativeLayout
+        android:id="@+id/auth_credential_header"
+        style="?headerStyle"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
+        android:layout_height="match_parent">
+
+        <ImageView
+            android:id="@+id/icon"
+            style="?headerIconStyle"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentTop="true"
+            android:contentDescription="@null"/>
+
+        <TextView
+            android:id="@+id/title"
+            style="?titleTextAppearance"
+            android:layout_below="@id/icon"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+
+        <TextView
+            android:id="@+id/subtitle"
+            style="?subTitleTextAppearance"
+            android:layout_below="@id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+
+        <TextView
+            android:id="@+id/description"
+            style="?descriptionTextAppearance"
+            android:layout_below="@id/subtitle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+    </RelativeLayout>
+
+    <LinearLayout
+        android:id="@+id/auth_credential_input"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
         android:orientation="vertical">
 
-        <LinearLayout
-            android:id="@+id/auth_credential_header"
-            style="@style/AuthCredentialHeaderStyle"
-            android:layout_width="match_parent"
+        <ImeAwareEditText
+            android:id="@+id/lockPassword"
+            style="?passwordTextAppearance"
+            android:layout_width="208dp"
             android:layout_height="wrap_content"
-            android:layout_alignParentTop="true">
+            android:layout_gravity="center_horizontal"
+            android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
+            android:inputType="textPassword"
+            android:minHeight="48dp" />
 
-            <ImageView
-                android:id="@+id/icon"
-                android:layout_width="48dp"
-                android:layout_height="48dp"
-                android:contentDescription="@null" />
-
-            <TextView
-                android:id="@+id/title"
-                style="@style/TextAppearance.AuthNonBioCredential.Title"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content" />
-
-            <TextView
-                android:id="@+id/subtitle"
-                style="@style/TextAppearance.AuthNonBioCredential.Subtitle"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content" />
-
-            <TextView
-                android:id="@+id/description"
-                style="@style/TextAppearance.AuthNonBioCredential.Description"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content" />
-
-        </LinearLayout>
-
-        <LinearLayout
+        <TextView
+            android:id="@+id/error"
+            style="?errorTextAppearance"
+            android:layout_gravity="center_horizontal"
             android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:gravity="center"
-            android:orientation="vertical"
-            android:layout_alignParentBottom="true">
+            android:layout_height="wrap_content" />
 
-            <ImeAwareEditText
-                android:id="@+id/lockPassword"
-                style="@style/TextAppearance.AuthCredential.PasswordEntry"
-                android:layout_width="208dp"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center"
-                android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
-                android:inputType="textPassword"
-                android:minHeight="48dp" />
-
-            <TextView
-                android:id="@+id/error"
-                style="@style/TextAppearance.AuthNonBioCredential.Error"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content" />
-
-        </LinearLayout>
-
-    </RelativeLayout>
+    </LinearLayout>
 
 </com.android.systemui.biometrics.AuthCredentialPasswordView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
index dada981..4af9970 100644
--- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
@@ -16,87 +16,66 @@
 
 <com.android.systemui.biometrics.AuthCredentialPatternView
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical"
-    android:gravity="center_horizontal"
-    android:elevation="@dimen/biometric_dialog_elevation">
+    android:elevation="@dimen/biometric_dialog_elevation"
+    android:theme="?app:attr/lockPatternStyle">
 
     <RelativeLayout
+        android:id="@+id/auth_credential_header"
+        style="?headerStyle"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical">
+        android:layout_height="wrap_content">
 
-        <LinearLayout
-            android:id="@+id/auth_credential_header"
-            style="@style/AuthCredentialHeaderStyle"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content">
+        <ImageView
+            android:id="@+id/icon"
+            style="?headerIconStyle"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentTop="true"
+            android:contentDescription="@null"/>
 
-            <ImageView
-                android:id="@+id/icon"
-                android:layout_width="48dp"
-                android:layout_height="48dp"
-                android:contentDescription="@null" />
+        <TextView
+            android:id="@+id/title"
+            style="?titleTextAppearance"
+            android:layout_below="@id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
 
-            <TextView
-                android:id="@+id/title"
-                style="@style/TextAppearance.AuthNonBioCredential.Title"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content" />
+        <TextView
+            android:id="@+id/subtitle"
+            style="?subTitleTextAppearance"
+            android:layout_below="@id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
 
-            <TextView
-                android:id="@+id/subtitle"
-                style="@style/TextAppearance.AuthNonBioCredential.Subtitle"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content" />
-
-            <TextView
-                android:id="@+id/description"
-                style="@style/TextAppearance.AuthNonBioCredential.Description"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content" />
-        </LinearLayout>
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_below="@id/auth_credential_header"
-            android:gravity="center"
-            android:orientation="vertical"
-            android:paddingBottom="16dp"
-            android:paddingTop="60dp">
-
-            <FrameLayout
-                style="@style/LockPatternContainerStyle"
-                android:layout_width="wrap_content"
-                android:layout_height="0dp"
-                android:layout_weight="1">
-
-                <com.android.internal.widget.LockPatternView
-                    android:id="@+id/lockPattern"
-                    style="@style/LockPatternStyle"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
-                    android:layout_gravity="center" />
-
-            </FrameLayout>
-
-        </LinearLayout>
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentBottom="true">
-
-            <TextView
-                android:id="@+id/error"
-                style="@style/TextAppearance.AuthNonBioCredential.Error"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content" />
-
-        </LinearLayout>
-
+        <TextView
+            android:id="@+id/description"
+            style="?descriptionTextAppearance"
+            android:layout_below="@id/subtitle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
     </RelativeLayout>
 
+    <FrameLayout
+        android:id="@+id/auth_credential_container"
+        style="?containerStyle"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <com.android.internal.widget.LockPatternView
+            android:id="@+id/lockPattern"
+            android:layout_gravity="center"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
+
+        <TextView
+            android:id="@+id/error"
+            style="?errorTextAppearance"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal|bottom"/>
+    </FrameLayout>
+
 </com.android.systemui.biometrics.AuthCredentialPatternView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_ttt_chip.xml b/packages/SystemUI/res/layout/chipbar.xml
similarity index 90%
rename from packages/SystemUI/res/layout/media_ttt_chip.xml
rename to packages/SystemUI/res/layout/chipbar.xml
index ae8e38e..bc97e51 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip.xml
+++ b/packages/SystemUI/res/layout/chipbar.xml
@@ -16,15 +16,15 @@
 <!-- Wrap in a frame layout so that we can update the margins on the inner layout. (Since this view
      is the root view of a window, we cannot change the root view's margins.) -->
 <!-- Alphas start as 0 because the view will be animated in. -->
-<com.android.systemui.media.taptotransfer.sender.MediaTttChipRootView
+<com.android.systemui.temporarydisplay.chipbar.ChipbarRootView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:id="@+id/media_ttt_sender_chip"
+    android:id="@+id/chipbar_root_view"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content">
 
     <LinearLayout
-        android:id="@+id/media_ttt_sender_chip_inner"
+        android:id="@+id/chipbar_inner"
         android:orientation="horizontal"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
@@ -39,7 +39,7 @@
         >
 
         <com.android.internal.widget.CachingIconView
-            android:id="@+id/app_icon"
+            android:id="@+id/start_icon"
             android:layout_width="@dimen/media_ttt_app_icon_size"
             android:layout_height="@dimen/media_ttt_app_icon_size"
             android:layout_marginEnd="12dp"
@@ -69,7 +69,7 @@
             />
 
         <ImageView
-            android:id="@+id/failure_icon"
+            android:id="@+id/error"
             android:layout_width="@dimen/media_ttt_status_icon_size"
             android:layout_height="@dimen/media_ttt_status_icon_size"
             android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
@@ -78,11 +78,11 @@
             android:alpha="0.0"
             />
 
+        <!-- TODO(b/245610654): Re-name all the media-specific dimens to chipbar dimens instead. -->
         <TextView
-            android:id="@+id/undo"
+            android:id="@+id/end_button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:text="@string/media_transfer_undo"
             android:textColor="?androidprv:attr/textColorOnAccent"
             android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
             android:textSize="@dimen/media_ttt_text_size"
@@ -97,4 +97,4 @@
             />
 
     </LinearLayout>
-</com.android.systemui.media.taptotransfer.sender.MediaTttChipRootView>
+</com.android.systemui.temporarydisplay.chipbar.ChipbarRootView>
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 1a1fc75..0e9abee 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.systemui.screenshot.DraggableConstraintLayout
+<com.android.systemui.clipboardoverlay.ClipboardOverlayView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
@@ -157,4 +157,4 @@
             android:layout_margin="@dimen/overlay_dismiss_button_margin"
             android:src="@drawable/overlay_cancel"/>
     </FrameLayout>
-</com.android.systemui.screenshot.DraggableConstraintLayout>
\ No newline at end of file
+</com.android.systemui.clipboardoverlay.ClipboardOverlayView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml b/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml
new file mode 100644
index 0000000..1a1fc75
--- /dev/null
+++ b/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml
@@ -0,0 +1,160 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<com.android.systemui.screenshot.DraggableConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/clipboard_ui"
+    android:theme="@style/FloatingOverlay"
+    android:alpha="0"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:contentDescription="@string/clipboard_overlay_window_name">
+    <ImageView
+        android:id="@+id/actions_container_background"
+        android:visibility="gone"
+        android:layout_height="0dp"
+        android:layout_width="0dp"
+        android:elevation="4dp"
+        android:background="@drawable/action_chip_container_background"
+        android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+        app:layout_constraintBottom_toBottomOf="@+id/actions_container"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="@+id/actions_container"
+        app:layout_constraintEnd_toEndOf="@+id/actions_container"/>
+    <HorizontalScrollView
+        android:id="@+id/actions_container"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
+        android:paddingEnd="@dimen/overlay_action_container_padding_right"
+        android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
+        android:elevation="4dp"
+        android:scrollbars="none"
+        android:layout_marginBottom="4dp"
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constraintWidth_percent="1.0"
+        app:layout_constraintWidth_max="wrap"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/preview_border"
+        app:layout_constraintEnd_toEndOf="parent">
+        <LinearLayout
+            android:id="@+id/actions"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:animateLayoutChanges="true">
+            <include layout="@layout/overlay_action_chip"
+                     android:id="@+id/share_chip"/>
+            <include layout="@layout/overlay_action_chip"
+                     android:id="@+id/remote_copy_chip"/>
+            <include layout="@layout/overlay_action_chip"
+                     android:id="@+id/edit_chip"/>
+        </LinearLayout>
+    </HorizontalScrollView>
+    <View
+        android:id="@+id/preview_border"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginStart="@dimen/overlay_offset_x"
+        android:layout_marginBottom="12dp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        android:elevation="7dp"
+        app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end"
+        app:layout_constraintTop_toTopOf="@id/clipboard_preview_top"
+        android:background="@drawable/overlay_border"/>
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/clipboard_preview_end"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierMargin="@dimen/overlay_border_width"
+        app:barrierDirection="end"
+        app:constraint_referenced_ids="clipboard_preview"/>
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/clipboard_preview_top"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierDirection="top"
+        app:barrierMargin="@dimen/overlay_border_width_neg"
+        app:constraint_referenced_ids="clipboard_preview"/>
+    <FrameLayout
+        android:id="@+id/clipboard_preview"
+        android:elevation="7dp"
+        android:background="@drawable/overlay_preview_background"
+        android:clipChildren="true"
+        android:clipToOutline="true"
+        android:clipToPadding="true"
+        android:layout_width="@dimen/clipboard_preview_size"
+        android:layout_margin="@dimen/overlay_border_width"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        app:layout_constraintBottom_toBottomOf="@id/preview_border"
+        app:layout_constraintStart_toStartOf="@id/preview_border"
+        app:layout_constraintEnd_toEndOf="@id/preview_border"
+        app:layout_constraintTop_toTopOf="@id/preview_border">
+        <TextView android:id="@+id/text_preview"
+                  android:textFontWeight="500"
+                  android:padding="8dp"
+                  android:gravity="center|start"
+                  android:ellipsize="end"
+                  android:autoSizeTextType="uniform"
+                  android:autoSizeMinTextSize="@dimen/clipboard_overlay_min_font"
+                  android:autoSizeMaxTextSize="@dimen/clipboard_overlay_max_font"
+                  android:textColor="?attr/overlayButtonTextColor"
+                  android:textColorLink="?attr/overlayButtonTextColor"
+                  android:background="?androidprv:attr/colorAccentSecondary"
+                  android:layout_width="@dimen/clipboard_preview_size"
+                  android:layout_height="@dimen/clipboard_preview_size"/>
+        <ImageView
+            android:id="@+id/image_preview"
+            android:scaleType="fitCenter"
+            android:adjustViewBounds="true"
+            android:contentDescription="@string/clipboard_image_preview"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+        <TextView
+            android:id="@+id/hidden_preview"
+            android:visibility="gone"
+            android:textFontWeight="500"
+            android:padding="8dp"
+            android:gravity="center"
+            android:textSize="14sp"
+            android:textColor="?attr/overlayButtonTextColor"
+            android:background="?androidprv:attr/colorAccentSecondary"
+            android:layout_width="@dimen/clipboard_preview_size"
+            android:layout_height="@dimen/clipboard_preview_size"/>
+    </FrameLayout>
+    <FrameLayout
+        android:id="@+id/dismiss_button"
+        android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
+        android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
+        android:elevation="10dp"
+        android:visibility="gone"
+        android:alpha="0"
+        app:layout_constraintStart_toEndOf="@id/clipboard_preview"
+        app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
+        app:layout_constraintTop_toTopOf="@id/clipboard_preview"
+        app:layout_constraintBottom_toTopOf="@id/clipboard_preview"
+        android:contentDescription="@string/clipboard_dismiss_description">
+        <ImageView
+            android:id="@+id/dismiss_image"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_margin="@dimen/overlay_dismiss_button_margin"
+            android:src="@drawable/overlay_cancel"/>
+    </FrameLayout>
+</com.android.systemui.screenshot.DraggableConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index 5dc34b9..a565988 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -73,8 +73,8 @@
         android:singleLine="true"
         android:textDirection="locale"
         android:textAppearance="@style/TextAppearance.QS.Status"
-        android:transformPivotX="0sp"
-        android:transformPivotY="20sp"
+        android:transformPivotX="0dp"
+        android:transformPivotY="24dp"
         android:scaleX="1"
         android:scaleY="1"
     />
diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml
index 50d3cc4..715c869 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.MediaScrollView
+    <com.android.systemui.media.controls.ui.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.MediaScrollView>
+    </com.android.systemui.media.controls.ui.MediaScrollView>
     <com.android.systemui.qs.PageIndicator
         android:id="@+id/media_page_indicator"
         android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index f0e49d5..92ef3f8 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -32,41 +32,8 @@
         android:layout_height="match_parent"
         android:layout_width="match_parent" />
 
-    <include
-        layout="@layout/keyguard_bottom_area"
-        android:visibility="gone" />
-
-    <ViewStub
-        android:id="@+id/keyguard_user_switcher_stub"
-        android:layout="@layout/keyguard_user_switcher"
-        android:layout_height="match_parent"
-        android:layout_width="match_parent" />
-
     <include layout="@layout/status_bar_expanded_plugin_frame"/>
 
-    <include layout="@layout/dock_info_bottom_area_overlay" />
-
-    <com.android.keyguard.LockIconView
-        android:id="@+id/lock_icon_view"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content">
-        <!-- Background protection -->
-        <ImageView
-            android:id="@+id/lock_icon_bg"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:background="@drawable/fingerprint_bg"
-            android:visibility="invisible"/>
-
-        <ImageView
-            android:id="@+id/lock_icon"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_gravity="center"
-            android:scaleType="centerCrop"/>
-
-    </com.android.keyguard.LockIconView>
-
     <com.android.systemui.shade.NotificationsQuickSettingsContainer
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -145,6 +112,39 @@
         />
     </com.android.systemui.shade.NotificationsQuickSettingsContainer>
 
+    <include
+        layout="@layout/keyguard_bottom_area"
+        android:visibility="gone" />
+
+    <ViewStub
+        android:id="@+id/keyguard_user_switcher_stub"
+        android:layout="@layout/keyguard_user_switcher"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent" />
+
+    <include layout="@layout/dock_info_bottom_area_overlay" />
+
+    <com.android.keyguard.LockIconView
+        android:id="@+id/lock_icon_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content">
+        <!-- Background protection -->
+        <ImageView
+            android:id="@+id/lock_icon_bg"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:background="@drawable/fingerprint_bg"
+            android:visibility="invisible"/>
+
+        <ImageView
+            android:id="@+id/lock_icon"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"
+            android:scaleType="centerCrop"/>
+
+    </com.android.keyguard.LockIconView>
+
     <FrameLayout
         android:id="@+id/preview_container"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index f07f10d..eccce2f 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -94,19 +94,14 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"ለአንድ የማያ ገጽ ቀረጻ ክፍለ-ጊዜ በመካሄድ ያለ ማሳወቂያ"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"መቅረጽ ይጀመር?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"እየቀረጹ ሳለ የAndroid ስርዓት በማያ ገጽዎ ላይ የሚታይ ወይም በመሣሪያዎ ላይ የሚጫወት ማንኛውም ሚስጥራዊነት ያለው መረጃን መያዝ ይችላል። ይህ የይለፍ ቃላትን፣ የክፍያ መረጃን፣ ፎቶዎችን፣ መልዕክቶችን እና ኦዲዮን ያካትታል።"</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"መላው ማያ ገጹን ቅረጽ"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"አንድ ነጠላ መተግበሪያን ቅረጽ"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"እየቀረጹ እያለ Android በማያ ገጽዎ ላይ ለሚታይ ወይም በመሣሪያዎ ላይ ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"አንድን መተግበሪያ እየቀረጹ ሳለ Android በዚያ መተግበሪያ ላይ ለሚታይ ወይም ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"መቅረጽ ጀምር"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"ኦዲዮን ቅረጽ"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"የመሣሪያ ኦዲዮ"</string>
-    <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"እንደ ሙዚቃ፣ ጥሪዎች እና የጥሪ ቅላጼዎች ያሉ የመሣሪያዎ ድምጽ"</string>
+    <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"እንደ ሙዚቃ፣ ጥሪዎች እና የጥሪ ቅላጼዎች ያሉ የመሣሪያዎ ድምፅ"</string>
     <string name="screenrecord_mic_label" msgid="2111264835791332350">"ማይክሮፎን"</string>
     <string name="screenrecord_device_audio_and_mic_label" msgid="1831323771978646841">"የመሣሪያ ኦዲዮ እና ማይክሮፎን"</string>
     <string name="screenrecord_start" msgid="330991441575775004">"ጀምር"</string>
@@ -274,14 +269,14 @@
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"የ<xliff:g id="DATA_LIMIT">%s</xliff:g> ማስጠንቀቂያ"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"የሥራ መተግበሪያዎች"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"የምሽት ብርሃን"</string>
-    <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"ጸሐይ ስትጠልቅ ይበራል"</string>
-    <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"ጸሐይ እስክትወጣ ድረስ"</string>
+    <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"ፀሐይ ስትጠልቅ ይበራል"</string>
+    <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"ፀሐይ እስክትወጣ ድረስ"</string>
     <string name="quick_settings_night_secondary_label_on_at" msgid="3584738542293528235">"<xliff:g id="TIME">%s</xliff:g> ላይ ይበራል"</string>
     <string name="quick_settings_secondary_label_until" msgid="1883981263191927372">"እስከ <xliff:g id="TIME">%s</xliff:g> ድረስ"</string>
     <string name="quick_settings_ui_mode_night_label" msgid="1398928270610780470">"ጨለማ ገጽታ"</string>
     <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"ባትሪ ቆጣቢ"</string>
-    <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"ጸሐይ ስትጠልቅ ይበራል"</string>
-    <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"ጸሐይ እስክትወጣ ድረስ"</string>
+    <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"ፀሐይ ስትጠልቅ ይበራል"</string>
+    <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"ፀሐይ እስክትወጣ ድረስ"</string>
     <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> ላይ ይበራል"</string>
     <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"እስከ <xliff:g id="TIME">%s</xliff:g> ድረስ"</string>
     <string name="quick_settings_dark_mode_secondary_label_on_at_bedtime" msgid="2274300599408864897">"በመኝታ ሰዓት ላይ"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ይህን ተግባር የሚያቀርበው አገልግሎት በእርስዎ ማያ ገጽ ላይ ያለን ወይም በእርስዎ መሣሪያ ላይ በመጫወት ላይ ያለን ሁሉንም መረጃ በቀረጻ ወይም casting ላይ እያለ መዳረሻ ይኖረዋል። ይህ እንደ የይለፍ ቃላት፣ የክፍያ ዝርዝሮች፣ ፎቶዎች፣ መልዕክቶች እና እርስዎ የሚጫውቱት ኦዲዮን የመሳሰለ መረጃን ያካትታል።"</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ቀረጻ ወይም cast ማድረግ ይጀምር?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"ከ<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ጋር ቀረጻ ወይም casting ይጀምር?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> እንዲያጋራ ወይም እንዲቀርጽ ይፈቀድለት?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"መላው ማያ ገጽ"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"አንድ ነጠላ መተግበሪያ"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> በማያ ገጽዎ ላይ ለሚታይ ወይም በመሣሪያዎ ላይ ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"አንድን መተግበሪያ ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> በዚያ መተግበሪያ ላይ ለሚታይ ወይም ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ቀጥል"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"መተግበሪያ ያጋሩ ወይም ይቅረጹ"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ሁሉንም አጽዳ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ያቀናብሩ"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ታሪክ"</string>
@@ -445,7 +433,7 @@
     <string name="volume_odi_captions_content_description" msgid="4172765742046013630">"የሥዕል መግለጫ ጽሑፎች ንብርብር"</string>
     <string name="volume_odi_captions_hint_enable" msgid="2073091194012843195">"አንቃ"</string>
     <string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"አሰናክል"</string>
-    <string name="sound_settings" msgid="8874581353127418308">"ድምጽ እና ንዝረት"</string>
+    <string name="sound_settings" msgid="8874581353127418308">"ድምፅ እና ንዝረት"</string>
     <string name="volume_panel_dialog_settings_button" msgid="2513228491513390310">"ቅንብሮች"</string>
     <string name="screen_pinning_title" msgid="9058007390337841305">"መተግበሪያ ተሰክቷል"</string>
     <string name="screen_pinning_description" msgid="8699395373875667743">"ይሄ እስኪነቅሉት ድረስ በእይታ ውስጥ ያስቀምጠዋል። ለመንቀል ተመለስ እና አጠቃላይ ዕይታ የሚለውን ይጫኑ እና ይያዙ።"</string>
@@ -527,11 +515,11 @@
     <string name="notification_silence_title" msgid="8608090968400832335">"ፀጥ ያለ"</string>
     <string name="notification_alert_title" msgid="3656229781017543655">"ነባሪ"</string>
     <string name="notification_automatic_title" msgid="3745465364578762652">"ራስ-ሰር"</string>
-    <string name="notification_channel_summary_low" msgid="4860617986908931158">"ምንም ድምጽ ወይም ንዝረት የለም"</string>
-    <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ምንም ድምጽ ወይም ንዝረት የለም እና በውይይት ክፍል ላይ አይታይም"</string>
+    <string name="notification_channel_summary_low" msgid="4860617986908931158">"ምንም ድምፅ ወይም ንዝረት የለም"</string>
+    <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ምንም ድምፅ ወይም ንዝረት የለም እና በውይይት ክፍል ላይ አይታይም"</string>
     <string name="notification_channel_summary_default" msgid="3282930979307248890">"በእርስዎ የስልክ ቅንብሮች የሚወሰን ሆኖ ሊደውል ወይም ሊነዝር ይችላል"</string>
     <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"በእርስዎ የስልክ ቅንብሮች የሚወሰን ሆኖ ሊደውል ወይም ሊነዝር ይችላል። የ<xliff:g id="APP_NAME">%1$s</xliff:g> አረፋ ውይይቶች በነባሪነት።"</string>
-    <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ይህ ማሳወቂያ ድምጽ ወይም ንዝረት መደረግ ካለበት ስርዓቱ እንዲወሰን ያድርጉት"</string>
+    <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ይህ ማሳወቂያ ድምፅ ወይም ንዝረት መደረግ ካለበት ስርዓቱ እንዲወሰን ያድርጉት"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;ሁኔታ:&lt;/b&gt; ለነባሪ ከፍ ተዋውቋል።"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;ሁኔታ:&lt;/b&gt; ወደ ዝምታ ዝቅ ተደርጓል"</string>
     <string name="notification_channel_summary_automatic_promoted" msgid="1301710305149590426">"&lt;b&gt;ሁኔታ:&lt;/b&gt; ክፍተኛ ደረጃ ተሰጥቶታል"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 732c2e6..8f996fb 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"إشعار مستمر لجلسة تسجيل شاشة"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"هل تريد بدء التسجيل؟"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"‏أثناء التسجيل، يمكن أن يسجّل نظام Android أي معلومات حساسة مرئية على شاشتك أو يتم تشغيلها على جهازك. ويشمل ذلك كلمات المرور ومعلومات الدفع والصور والرسائل والمقاطع الصوتية."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"تسجيل الشاشة بالكامل"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"تسجيل محتوى تطبيق واحد"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"‏أثناء التسجيل، يمكن لنظام Android الوصول إلى كل العناصر المرئية على شاشتك أو التي يتم تشغيلها على جهازك، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"‏أثناء تسجيل محتوى أحد التطبيقات، يمكن لنظام Android الوصول إلى كل العناصر المعروضة أو التي يتم تشغيلها في ذلك التطبيق، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"بدء التسجيل"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"تسجيل الصوت"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"صوت الجهاز"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"الصوت من جهازك، مثلاً الموسيقى والمكالمات ونغمات الرنين"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ستتمكن الخدمة التي تقدّم هذه الوظيفة من الوصول إلى كل المعلومات المرئية لك على الشاشة أو التي يتم تشغيلها على جهازك أثناء التسجيل أو الإرسال. ويشمل ذلك معلومات مثل كلمات المرور وتفاصيل الدفع والصور والرسائل والمقاطع الصوتية التي تشغِّلها."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"هل تريد بدء التسجيل أو الإرسال؟"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"هل تريد بدء التسجيل أو الإرسال باستخدام <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>؟"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"هل تريد السماح لتطبيق <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>بالمشاركة أو التسجيل؟"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"الشاشة بالكامل"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"تطبيق واحد"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"أثناء المشاركة أو التسجيل أو البث، يمكن لتطبيق <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> الوصول إلى كل العناصر المرئية على شاشتك أو التي يتم تشغيلها على جهازك، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"أثناء مشاركة محتوى تطبيق أو تسجيله أو بثه، يمكن لتطبيق <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> الوصول إلى كل العناصر المعروضة أو التي يتم تشغيلها في ذلك التطبيق، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"متابعة"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"مشاركة محتوى تطبيق أو تسجيله"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"محو الكل"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"إدارة"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"السجلّ"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index c2f8e43..fe95fa0 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"স্ক্রীন ৰেকৰ্ডিং ছেশ্বন চলি থকা সময়ত পোৱা জাননী"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"ৰেকৰ্ড কৰা আৰম্ভ কৰিবনে?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"ৰেকৰ্ড কৰি থাকোঁতে, Android Systemএ আপোনাৰ স্ক্রীনত দৃশ্যমান হোৱা অথবা আপোনাৰ ডিভাইচত প্লে’ হৈ থকা যিকোনো সংবেনদশীল তথ্য কেপচাৰ কৰিব পাৰে। এইটোত পাছৱর্ড, পৰিশোধৰ তথ্য, ফট’, বার্তাসমূহ আৰু অডিঅ’ অন্তর্ভুক্ত হয়।"</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"গোটেই স্ক্ৰীনখন ৰেকৰ্ড কৰক"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"এটা এপ্ ৰেকৰ্ড কৰক"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"আপুনি ৰেকৰ্ড কৰাৰ সময়ত, আপোনাৰ স্ক্ৰীনখনত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ Androidৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"আপুনি এপ এপ্ ৰেকৰ্ড কৰাৰ সময়ত সেইটো এপত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ Androidৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"ৰেকৰ্ডিং আৰম্ভ কৰক"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"অডিঅ’ ৰেকৰ্ড কৰক"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ডিভাইচৰ অডিঅ’"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"সংগীত, কল আৰু ৰিংট’নসমূহৰ দৰে আপোনাৰ ডিভাইচৰ পৰা কেপচাৰ কৰিব পৰা ধ্বনি"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"এই সুবিধাটো প্ৰদান কৰা সেৱাটোৱে আপোনাৰ স্ক্ৰীনত দৃশ্যমান হোৱা অথবা ৰেকর্ডিং অথবা কাষ্টিঙৰ সময়ত আপোনাৰ ডিভাইচত প্লে\' কৰা আটাইবোৰ তথ্যলৈ এক্সেছ পাব। এইটোত পাছৱর্ড, পৰিশোধৰ সবিশেষ, ফট\', বার্তাসমূহ আৰু আপুনি প্লে\' কৰা অডিঅ\'ৰ দৰে তথ্য অন্তর্ভুক্ত হয়।"</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ৰেকর্ডিং অথবা কাষ্টিং আৰম্ভ কৰিবনে?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ৰ জৰিয়তে ৰেকর্ডিং অথবা কাষ্টিং আৰম্ভ কৰিবনে ?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ক শ্বেয়াৰ অথবা ৰেকৰ্ড কৰিবলৈ অনুমতি দিবনে?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"সম্পূৰ্ণ স্ক্ৰীন"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"এটা একক এপ্"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, আপোনাৰ স্ক্ৰীনখনত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, সেইটো এপত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"অব্যাহত ৰাখক"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"এটা এপ্ শ্বেয়াৰ অথবা ৰেকৰ্ড কৰক"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"আটাইবোৰ মচক"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"পৰিচালনা"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ইতিহাস"</string>
@@ -575,7 +563,7 @@
     <string name="keyboard_key_dpad_down" msgid="2110172278574325796">"তললৈ"</string>
     <string name="keyboard_key_dpad_left" msgid="8329738048908755640">"বাওঁফালে"</string>
     <string name="keyboard_key_dpad_right" msgid="6282105433822321767">"সোঁফালে"</string>
-    <string name="keyboard_key_dpad_center" msgid="4079412840715672825">"স্ক্ৰীণৰ মাজত"</string>
+    <string name="keyboard_key_dpad_center" msgid="4079412840715672825">"স্ক্ৰীনৰ মাজত"</string>
     <string name="keyboard_key_tab" msgid="4592772350906496730">"Tab"</string>
     <string name="keyboard_key_space" msgid="6980847564173394012">"স্পেচ"</string>
     <string name="keyboard_key_enter" msgid="8633362970109751646">"এণ্টাৰ"</string>
@@ -596,7 +584,7 @@
     <string name="keyboard_key_numpad_template" msgid="7316338238459991821">"নামপেড <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="notif_inline_reply_remove_attachment_description" msgid="7954075334095405429">"সংলগ্নক আঁতৰাওক"</string>
     <string name="keyboard_shortcut_group_system" msgid="1583416273777875970">"ছিষ্টেম"</string>
-    <string name="keyboard_shortcut_group_system_home" msgid="7465138628692109907">"গৃহ স্ক্ৰীণ"</string>
+    <string name="keyboard_shortcut_group_system_home" msgid="7465138628692109907">"গৃহ স্ক্ৰীন"</string>
     <string name="keyboard_shortcut_group_system_recents" msgid="8628108256824616927">"শেহতীয়াসমূহ"</string>
     <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"উভতি যাওক"</string>
     <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"জাননীসমূহ"</string>
@@ -693,7 +681,7 @@
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"যত্ন লোৱাৰ পদক্ষেপসমূহ চাওক"</string>
     <string name="high_temp_title" msgid="2218333576838496100">"ফ\'নটো গৰম হ\'বলৈ ধৰিছে"</string>
     <string name="high_temp_notif_message" msgid="1277346543068257549">"ফ’নটো ঠাণ্ডা হৈ থকাৰ সময়ত কিছুমান সুবিধা উপলব্ধ নহয়।\nঅধিক তথ্যৰ বাবে টিপক"</string>
-    <string name="high_temp_dialog_message" msgid="3793606072661253968">"আপোনাৰ ফ\'নটোৱে নিজে নিজে ঠাণ্ডা হ\'বলৈ স্বয়ংক্ৰিয়ভাৱে চেষ্টা কৰিব। আপুনি ফ\'নটো ব্যৱহাৰ কৰি থাকিব পাৰে কিন্তু ই লাহে লাহে চলিব পাৰে।\n\nফ\'নটো সম্পূৰ্ণভাৱে ঠাণ্ডা হোৱাৰ পিছত ই আগৰ নিচিনাকৈয়েই চলিব।"</string>
+    <string name="high_temp_dialog_message" msgid="3793606072661253968">"আপোনাৰ ফ\'নটোৱে নিজে নিজে ঠাণ্ডা হ\'বলৈ স্বয়ংক্ৰিয়ভাৱে চেষ্টা কৰিব। আপুনি ফ\'নটো ব্যৱহাৰ কৰি থাকিব পাৰে কিন্তু ই লাহে লাহে চলিব পাৰে।\n\nফ\'নটো সম্পূৰ্ণভাৱে ঠাণ্ডা হোৱাৰ পাছত ই আগৰ নিচিনাকৈয়েই চলিব।"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"যত্ন লোৱাৰ পদক্ষেপসমূহ চাওক"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"আপোনাৰ ডিভাইচটো আনপ্লাগ কৰক"</string>
     <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"আপোনাৰ ডিভাইচটো চাৰ্জিং প’ৰ্টৰ ওচৰত গৰম হৈছে। যদি এইটো কোনো চার্জাৰ অথবা ইউএছবিৰ সহায়ক সামগ্ৰীৰ সৈতে সংযুক্ত হৈ আছে, ইয়াক আনপ্লাগ কৰক আৰু কে’বলডালো গৰম হ\'ব পাৰে, গতিকে যত্ন লওক।"</string>
@@ -722,7 +710,7 @@
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> চলি আছে"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"এপ্‌টো ইনষ্ট\'ল নকৰাকৈ খোলা হৈছে।"</string>
-    <string name="instant_apps_message_with_help" msgid="1816952263531203932">"ইনষ্ট\'ল নকৰাকৈয়েই এপটো খোলা হৈছে। অধিক জানিবলৈ টিপক।"</string>
+    <string name="instant_apps_message_with_help" msgid="1816952263531203932">"ইনষ্ট\'ল নকৰাকৈয়েই এপ্‌টো খোলা হৈছে। অধিক জানিবলৈ টিপক।"</string>
     <string name="app_info" msgid="5153758994129963243">"এপৰ তথ্য"</string>
     <string name="go_to_web" msgid="636673528981366511">"ব্ৰাউজাৰলৈ যাওক"</string>
     <string name="mobile_data" msgid="4564407557775397216">"ম’বাইল ডেটা"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index fa6da94..cbc7c8a 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Текущо известие за сесия за записване на екрана"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Да се стартира ли записът?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"По време на записване системата Android може да запише и поверителна информация, която е показана на екрана или възпроизвеждана на устройството ви. Това включва пароли, данни за плащане, снимки, съобщения и аудио."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Записване на целия екран"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Записване на едно приложение"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Когато записвате, Android има достъп до всичко, което се вижда на екрана ви или се възпроизвежда на устройството ви, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Когато записвате приложение, Android има достъп до всичко, което се показва или възпроизвежда в това приложение, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Стартиране на записа"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Записване на звук"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Аудио от устройството"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Звук от устройството ви, като например музика, обаждания и мелодии"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Услугата, предоставяща тази функция, ще има достъп до цялата информация, която е видима на екрана или възпроизвеждана от устройството ви по време на записване или предаване. Това включва различна информация, като например пароли, данни за плащане, снимки, съобщения и възпроизвеждано аудио."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Да се стартира ли записване или предаване?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Да се стартира ли записване или предаване чрез <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Разрешавате ли на <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> да споделя и записва?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Цял екран"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Едно приложение"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Когато споделяте, записвате или предавате, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има достъп до всичко, което се вижда на екрана ви или се възпроизвежда на устройството ви, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Когато споделяте, записвате или предавате, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има достъп до всичко, което се показва или възпроизвежда в това приложение, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Напред"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Споделяне или записване на приложение"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Изчистване на всички"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Управление"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"История"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 8dc5f33..44fb0e6 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"স্ক্রিন রেকর্ডিং সেশন চলার বিজ্ঞপ্তি"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"রেকর্ডিং শুরু করবেন?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"রেকর্ড করার সময়, আপনার স্ক্রিনে দেখানো বা ডিভাইসে চালানো যেকোনও ধরনের সংবেদনশীল তথ্য Android সিস্টেম ক্যাপচার করতে পারে। এর মধ্যে পাসওয়ার্ড, পেমেন্টের তথ্য, ফটো, মেসেজ এবং অডিও সম্পর্কিত তথ্য থাকে।"</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"সম্পূর্ণ স্ক্রিন রেকর্ড করুন"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"একটিমাত্র অ্যাপ রেকর্ড করুন"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"আপনার রেকর্ড করার সময়, স্ক্রিনে দেখা যায় বা ডিভাইসে খেলা হয় এমন সব কিছু অ্যাক্সেস করার অনুমতি Android-এর আছে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"কোনও অ্যাপ আপনার রেকর্ড করার সময়, সেই অ্যাপে দেখা যায় বা খেলা হয় এমন সব কিছু অ্যাক্সেস করার অনুমতি Android-এর আছে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"রেকর্ড করা শুরু করুন"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"অডিও রেকর্ড করুন"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ডিভাইস অডিও"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"মিউজিক, কল এবং রিংটোনগুলির মতো আপনার ডিভাইস থেকে সাউন্ড"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"রেকর্ড করা বা কাস্টিং করার সময় আপনার স্ক্রিনে দেখানো বা ডিভাইসে চালানো হয়েছে এমন সমস্ত তথ্যের অ্যাক্সেস এই ফাংশন প্রদানকারী পরিষেবার কাছে থাকবে। এর মধ্যে আপনার পাসওয়ার্ড, পেমেন্টের বিবরণ, ফটো, মেসেজ এবং যে অডিও আপনি চালান সেগুলি সম্পর্কিত তথ্য রয়েছে।"</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"রেকর্ড অথবা কাস্টিং শুরু করতে চান?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> দিয়ে রেকর্ড করা বা কাস্টিং শুরু করবেন?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-কে শেয়ার বা রেকর্ড করার অনুমতি দেবেন?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"সম্পূর্ণ স্ক্রিন"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"একটি মাত্র অ্যাপ"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"আপনার শেয়ার করা, রেকর্ড করা বা কাস্ট করার সময়, স্ক্রিনে দেখা যায় বা ডিভাইসে খেলা হয় এমন সব কিছু অ্যাক্সেস করার অনুমতি <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-এর আছে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"কোনও অ্যাপ আপনার শেয়ার করা, রেকর্ড করা বা কাস্ট করার সময়, সেই অ্যাপে দেখা যায় বা খেলা হয় এমন সব কিছু অ্যাক্সেস করার অনুমতি <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-এর আছে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"চালিয়ে যান"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"অ্যাপ শেয়ার বা রেকর্ড করা"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"সবকিছু সাফ করুন"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"পরিচালনা করুন"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ইতিহাস"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 5e3fb33..172b14d 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificació en curs d\'una sessió de gravació de la pantalla"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Vols iniciar la gravació?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Durant la gravació, el sistema Android pot capturar qualsevol informació sensible que es mostri a la pantalla o que es reprodueixi al dispositiu. Això inclou contrasenyes, informació de pagament, fotos, missatges i àudio."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Grava la pantalla completa"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Grava una sola aplicació"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Mentre graves, Android té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi al dispositiu. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Mentre graves una aplicació, Android té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi a l\'aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Inicia la gravació"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Grava l\'àudio"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Àudio del dispositiu"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"So del dispositiu, com ara música, trucades i sons de trucada"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"El servei que ofereix aquesta funció tindrà accés a tota la informació visible a la teva pantalla o que es reprodueix al dispositiu mentre graves o emets contingut, com ara contrasenyes, detalls dels pagaments, fotos, missatges i àudio que reprodueixis."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vols començar a gravar o emetre contingut?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Vols començar a gravar o emetre contingut amb <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vols permetre que <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> comparteixi o gravi?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Tota la pantalla"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Una sola aplicació"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Quan estàs compartint, gravant o emetent, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi al dispositiu. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quan estàs compartint, gravant o emetent, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi a l\'aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continua"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Comparteix o grava una aplicació"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Esborra-ho tot"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gestiona"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index b22656c..034fe9a 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Trvalé oznámení o relaci nahrávání"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Spustit nahrávání?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Při nahrávání může systém Android zaznamenávat citlivé údaje, které jsou viditelné na obrazovce nebo které jsou přehrávány na zařízení. Týká se to hesel, údajů o platbě, fotek, zpráv a zvuků."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Nahrát celou obrazovku"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Nahrát samostatnou aplikaci"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Během nahrávání má Android přístup k veškerému obsahu, který je viditelný na obrazovce nebo se přehrává v zařízení. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Během nahrávání aplikace má Android přístup k veškerému obsahu, který je v této aplikaci zobrazen nebo přehráván. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Spustit nahrávání"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Nahrávat zvuk"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Zvuk zařízení"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Zvuk ze zařízení, například hudba, hovory a vyzvánění"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Služba, která tuto funkci poskytuje, bude mít při nahrávání nebo odesílání přístup ke všem informacím, které jsou viditelné na obrazovce nebo které jsou přehrávány ze zařízení. Týká se to i hesel, údajů o platbě, fotek, zpráv a přehrávaných zvuků."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Začít nahrávat nebo odesílat?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Začít nahrávat nebo odesílat s aplikací <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Povolit aplikaci <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> sdílení nebo nahrávání?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Celá obrazovka"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Samostatná aplikace"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Když sdílíte, nahráváte nebo odesíláte obsah, aplikace <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> má přístup k veškerému obsahu, který je viditelný na obrazovce nebo se přehrává v zařízení. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Když sdílíte, nahráváte nebo odesíláte aplikaci, aplikace <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> má přístup k veškerému obsahu, který je v této aplikaci zobrazen nebo přehráván. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Pokračovat"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Sdílení nebo nahrání aplikace"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Smazat vše"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Spravovat"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historie"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 9edb3377..03d12af 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Konstant notifikation om skærmoptagelse"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Vil du starte optagelse?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Når du optager, kan Android-systemet registrere følsomme oplysninger, der er synlige på din skærm, eller som afspilles på din enhed. Dette inkluderer adgangskoder, betalingsoplysninger, fotos, meddelelser og lyd."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Optag hele skærmen"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Optag én app"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Mens du optager, har Android adgang til alt, der er synligt på din skærm eller afspilles på din enhed. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Mens du optager en app, har Android adgang til alt, der vises eller afspilles i den pågældende app. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Start optagelse"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Optag lyd"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Enhedslyd"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Lyd fra din enhed såsom musik, opkald og ringetoner"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Tjenesten, der tilbyder denne funktion, får adgang til alle de oplysninger, der er synlige på din skærm, eller som afspilles på din enhed, når du optager eller caster. Dette omfatter oplysninger som f.eks. adgangskoder, betalingsoplysninger, billeder, beskeder og afspillet lyd."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vil du begynde at optage eller caste?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Vil du begynde at optage eller caste via <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vil du tillade, at <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> deler eller optager?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Hele skærmen"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Én app"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Når du deler, optager eller caster, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> adgang til alt, der er synligt på din skærm eller afspilles på din enhed. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Når du deler, optager eller caster en app, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> adgang til alt, der vises eller afspilles i den pågældende app. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Fortsæt"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Del eller optag en app"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Ryd alle"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Administrer"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historik"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 7698065..1d13c87 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Fortlaufende Benachrichtigung für eine Bildschirmaufzeichnung"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Aufzeichnung starten?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Beim Aufnehmen kann das Android-System vertrauliche Informationen erfassen, die auf deinem Bildschirm angezeigt oder von deinem Gerät wiedergegeben werden. Das können Passwörter, Zahlungsinformationen, Fotos, Nachrichten und Audioinhalte sein."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Gesamten Bildschirm aufnehmen"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Eine einzelne App aufnehmen"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Während der Aufnahme hat Android Zugriff auf alle Inhalte, die auf dem Bildschirm sichtbar sind oder auf dem Gerät wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Während der Aufnahme einer App hat Android Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Aufnahme starten"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Audio aufnehmen"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio des Geräts"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Audioinhalte auf deinem Gerät, wie Musik, Anrufe und Klingeltöne"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Der Anbieter dieser App erhält Zugriff auf alle Informationen, die auf deinem Bildschirm sichtbar sind oder von deinem Gerät wiedergegeben werden, während du aufnimmst oder streamst. Dazu gehören beispielsweise angezeigte Passwörter, Zahlungsdetails, Fotos, Nachrichten und Audioinhalte."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Aufnahme oder Stream starten?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Aufnehmen oder Streamen mit der App \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" starten?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Zulassen, dass <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> Inhalte teilt oder aufnimmt?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Gesamter Bildschirm"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Eine einzelne App"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Beim Teilen, Aufnehmen oder Übertragen hat <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> Zugriff auf alle Inhalte, die auf dem Bildschirm sichtbar sind oder auf dem Gerät wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Beim Teilen, Aufnehmen oder Übertragen einer App hat <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Weiter"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"App teilen oder aufnehmen"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Alle löschen"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Verwalten"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Verlauf"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index e9d1534..0df5f16 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificación continua de una sesión de grabación de la pantalla"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"¿Empezar a grabar?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Mientras grabas, el sistema Android puede capturar información sensible que se muestre o se reproduzca en tu dispositivo, como contraseñas, datos de pago, fotos, mensajes y audio."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Grabar toda la pantalla"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Grabar una sola aplicación"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Mientras grabes contenido, Android podrá acceder a todo lo que sea visible en tu pantalla o que reproduzcas en tu dispositivo. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Mientras grabes una aplicación, Android podrá acceder a todo lo que muestre o reproduzca la aplicación. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Iniciar grabación"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Grabar audio"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio del dispositivo"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sonido de tu dispositivo, como música, llamadas y tonos de llamada"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"El servicio que ofrece esta función tendrá acceso a toda la información que se muestre en la pantalla o se reproduzca en el dispositivo mientras grabas o envías contenido, incluyendo contraseñas, detalles de pagos, fotos, mensajes y audios que reproduzcas."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"¿Empezar a grabar o enviar contenido?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"¿Iniciar grabación o el envío de contenido en <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"¿Permitir que <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> comparta o grabe contenido?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Toda la pantalla"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Una sola aplicación"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Cuando compartas, grabes o envíes contenido, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> podrá acceder a todo lo que sea visible en tu pantalla o que reproduzcas en tu dispositivo. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Cuando compartas, grabes o envíes una aplicación, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> podrá acceder a todo lo que muestre o reproduzca la aplicación. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartir o grabar una aplicación"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Borrar todo"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gestionar"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index fae8364..d42b397 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Pooleli märguanne ekraanikuva salvestamise seansi puhul"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Kas alustada salvestamist?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Salvestamise ajal võib Androidi süsteem jäädvustada tundlikku teavet, mis on ekraanikuval nähtav või mida seadmes esitatakse. See hõlmab paroole, makseteavet, fotosid, sõnumeid ja heli."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Kogu ekraanikuva salvestamine"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Ühe rakenduse salvestamine"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Salvestamise ajal on Androidil juurdepääs kõigele, mis on teie ekraanikuval nähtaval või mida teie seadmes esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Rakenduse salvestamise ajal on Androidil juurdepääs kõigele, mis on selles rakenduses nähtaval või mida selles esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Alusta salvestamist"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Heli salvestamine"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Seadme heli"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Seadmest pärinev heli, nt muusika, kõned ja helinad"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Seda funktsiooni pakkuv teenus saab juurdepääsu kogu teabele, mis on teie ekraanikuval nähtav või mida seadmes salvestamise või ülekande ajal esitatakse. See hõlmab teavet, nagu paroolid, maksete üksikasjad, fotod, sõnumid ja esitatav heli."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Kas alustada salvestamist või ülekannet?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Kas alustada rakendusega <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> salvestamist või ülekannet?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Kas lubada rakendusel <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> jagada või salvestada?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Kogu ekraanikuva"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Üks rakendus"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Kui jagate, salvestate või kannate üle, on rakendusel <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> juurdepääs kõigele, mis on teie ekraanikuval nähtaval või mida teie seadmes esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kui jagate, salvestate või kannate rakendust üle, on rakendusel <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> juurdepääs kõigele, mida selles rakenduses kuvatakse või esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Jätka"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Rakenduse jagamine või salvestamine"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tühjenda kõik"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Haldamine"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Ajalugu"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index cf53e32..fc93a80 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Pantailaren grabaketa-saioaren jakinarazpen jarraitua"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Grabatzen hasi nahi duzu?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Pantaila grabatzen duzun bitartean, baliteke Android sistemak pantailan agertzen den edo gailuak erreproduzitzen duen kontuzko informazioa grabatzea; besteak beste, pasahitzak, ordainketa-informazioa, argazkiak, mezuak eta audioa."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Grabatu pantaila osoko edukia"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Grabatu aplikazio bakar bat"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Grabatzen ari zarenean, pantailan ikusgai dagoen edo gailuan erreproduzitzen ari den guztirako sarbidea du Android-ek. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Aplikazio bat grabatzen ari zarenean, aplikazio horretan ikusgai dagoen edo bertan erreproduzitzen ari den guztirako sarbidea du Android-ek. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Hasi grabatzen"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Grabatu audioa"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Gailuaren audioa"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Gailuko soinuak; adibidez, musika, deiak eta tonuak"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Zerbait grabatzen edo igortzen duzunean, pantailan ikus daitekeen edo gailuak erreproduzitzen duen informazio guztia atzitu ahalko du funtzio hori eskaintzen duen zerbitzuak; besteak beste, pasahitzak, ordainketen xehetasunak, argazkiak, mezuak eta erreproduzitzen dituzun audioak."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Grabatzen edo igortzen hasi nahi duzu?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aplikazioarekin grabatzen edo igortzen hasi nahi duzu?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Edukia partekatu edo grabatzeko baimena eman nahi diozu <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aplikazioari?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Pantaila osoa"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Aplikazio bakar bat"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Edukia partekatzen, grabatzen edo igortzen ari zarenean, pantailan ikusgai dagoen edo gailuan erreproduzitzen ari den guztirako sarbidea du <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aplikazioak. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Aplikazio bat partekatzen, grabatzen edo igortzen ari zarenean, aplikazio horretan ikusgai dagoen edo bertan erreproduzitzen ari den guztirako sarbidea du <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aplikazioak. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Egin aurrera"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partekatu edo grabatu aplikazioak"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Garbitu guztiak"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Kudeatu"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 14a65dc..0882342 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Pysyvä ilmoitus näytön tallentamisesta"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Aloitetaanko tallennus?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Tallennuksen aikana Android-järjestelmä voi tallentaa mitä tahansa näytöllä näkyvää tai laitteen toistamaa arkaluontoista tietoa. Näitä tietoja ovat esimerkiksi salasanat, maksutiedot, kuvat, viestit ja audio."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Tallenna koko näyttö"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Tallenna yhtä sovellusta"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Kun tallennat, Android saa pääsyn kaikkeen näytölläsi näkyvään tai laitteellasi toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Kun tallennat sovellusta, Android saa pääsyn kaikkeen sovelluksessa näkyvään tai toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Aloita tallennus"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Tallenna audiota"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Laitteen audio"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Musiikki, puhelut, soittoäänet ja muut äänet laitteesta"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Ominaisuuden tarjoavalla palvelulla on pääsy kaikkiin näytölläsi näkyviin tietoihin ja tietoihin laitteesi toistamasta sisällöstä tallennuksen tai striimauksen aikana. Näitä tietoja ovat esimerkiksi salasanat, maksutiedot, kuvat, viestit ja toistettava audiosisältö."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Aloitetaanko tallentaminen tai striimaus?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Haluatko, että <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aloittaa tallennuksen tai striimauksen?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Sallitaanko, että <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> jaetaan tai tallennetaan?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Koko näyttö"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Yksittäinen sovellus"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Kun jaat, tallennat tai striimaat, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> saa pääsyn kaikkeen näytölläsi näkyvään tai laitteellasi toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kun jaat, tallennat tai striimaat sovellusta, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> saa pääsyn kaikkeen sovelluksessa näkyvään tai toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Jatka"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Jaa sovellus tai tallenna sen sisältöä"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tyhjennä kaikki"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Muuta asetuksia"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 9fdae3e..a80b037 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notification en cours pour une session d\'enregistrement de l\'écran"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Démarrer l\'enregistrement ?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Durant l\'enregistrement, le système Android peut capturer les infos sensibles affichées à l\'écran ou lues sur votre appareil. Cela inclut les mots de passe, les infos de paiement, les photos, les messages et l\'audio."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Enregistrer tout l\'écran"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Enregistrer une seule appli"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Lorsque vous enregistrez une appli, Android à accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Lorsque vous enregistrez une appli, Android a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Lancer l\'enregistrement"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Enregistrer l\'audio"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio de l\'appareil"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Son provenant de l\'appareil (musique, appels et sonneries, etc.)"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Le service qui fournit cette fonction aura accès à toutes les infos visibles sur votre écran ou lues depuis votre appareil lors d\'un enregistrement ou de la diffusion d\'un contenu. Cela comprend, entre autres, vos mots de passe, les détails de vos paiements, vos photos, vos messages ou les contenus audio que vous écoutez."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Démarrer l\'enregistrement ou la diffusion ?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Démarrer l\'enregistrement ou la diffusion avec <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Autoriser <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> à partager ou enregistrer ?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Tout l\'écran"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Une seule appli"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Lorsque vous partagez, enregistrez ou castez, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Lorsque vous partagez, enregistrez ou castez une appli, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuer"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partager ou enregistrer une appli"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gérer"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historique"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 36036aa..bb3894a 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificación en curso sobre unha sesión de gravación de pantalla"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Queres iniciar a gravación?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Durante a gravación, o sistema Android pode captar información confidencial que apareza na pantalla ou se reproduza no dispositivo, como contrasinais, información de pago, fotos, mensaxes e audio."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Gravar pantalla completa"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Gravar unha soa aplicación"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Durante a gravación, Android ten acceso a todo o que se vexa na pantalla ou se reproduza no teu dispositivo. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Cando gravas unha aplicación, Android ten acceso a todo o que se vexa ou se reproduza nela. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Iniciar gravación"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Gravar audio"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio do dispositivo"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Son do dispositivo (por exemplo, música, chamadas e tons de chamada)"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"O servizo que proporciona esta función terá acceso a toda a información visible na pantalla ou reproducida desde o teu dispositivo mentres graves ou emitas contido. Isto inclúe información como contrasinais, detalles de pago, fotos, mensaxes e o audio que reproduzas."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Queres iniciar a gravación ou a emisión?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Queres comezar a gravar ou emitir contido con <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Queres permitir que <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> comparta ou grave contido?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Pantalla completa"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Unha soa aplicación"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Cando compartes, gravas ou emites contido, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ten acceso a todo o que se vexa na pantalla ou se reproduza no teu dispositivo. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Cando compartes, gravas ou emites unha aplicación, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ten acceso a todo o que se vexa ou se reproduza nela. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartir ou gravar unha aplicación"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Eliminar todas"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Xestionar"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 3d1b1ea..9dc6f67 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"स्क्रीन रिकॉर्ड सेशन के लिए जारी सूचना"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"क्या आपको रिकॉर्डिंग शुरू करनी है?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"रिकॉर्ड करते समय, Android सिस्टम आपकी स्क्रीन पर दिखने वाली या चलाई जाने वाली संवेदनशील जानकारी को कैप्चर कर सकता है. इसमें पासवर्ड, पैसे चुकाने से जुड़ी जानकारी, फ़ोटो, मैसेज, और ऑडियो शामिल हैं."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"पूरी स्क्रीन रिकॉर्ड करें"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"सिर्फ़ एक ऐप्लिकेशन रिकॉर्ड करें"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Android के पास, रिकॉर्ड करने के दौरान, स्क्रीन पर दिख रही हर चीज़ या डिवाइस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, शेयर, रिकॉर्ड या कास्ट करते समय, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"कोई ऐप्लिकेशन रिकॉर्ड करने के दौरान, Android के पास उस पर दिख रही हर चीज़ या उस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"रिकॉर्ड करना शुरू करें"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"ऑडियो रिकॉर्ड करें"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"डिवाइस ऑडियो"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"आपके डिवाइस से आने वाली आवाज़ जैसे कि संगीत, कॉल, और रिंगटोन"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"इस फ़ंक्शन को उपलब्ध कराने वाली सेवा, रिकॉर्ड या कास्ट करते समय, आपकी स्क्रीन पर दिखने वाली या चलाई जाने वाली जानकारी को ऐक्सेस कर सकती है. इसमें पासवर्ड, पैसे चुकाने से जुड़ी जानकारी, फ़ोटो, मैसेज, और चलाए जाने वाले ऑडियो शामिल हैं."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"रिकॉर्डिंग या कास्ट करना शुरू करें?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> का इस्तेमाल करके रिकॉर्ड और कास्ट करना शुरू करें?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> को शेयर या रिकॉर्ड करने की अनुमति दें?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"पूरी स्क्रीन"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"सिर्फ़ एक ऐप्लिकेशन"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"शेयर, रिकॉर्ड या कास्ट करते समय, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> के पास स्क्रीन पर दिख रही हर चीज़ या डिवाइस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, शेयर, रिकॉर्ड या कास्ट करते समय, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"शेयर, रिकॉर्ड या कास्ट करते समय, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> के पास उस ऐप्लिकेशन पर दिख रही हर चीज़ या उस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, शेयर, रिकॉर्ड या कास्ट करते समय, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"जारी रखें"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ऐप्लिकेशन शेयर करें या उसकी रिकॉर्डिंग करें"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"सभी को हटाएं"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"मैनेज करें"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index fed009f..5e5e249 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Էկրանի տեսագրման աշխատաշրջանի ընթացիկ ծանուցում"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Սկսե՞լ տեսագրումը"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Տեսագրման ընթացքում Android համակարգը կարող է գրանցել անձնական տեղեկություններ, որոնք տեսանելի են էկրանին կամ նվագարկվում են ձեր սարքում։ Սա ներառում է այնպիսի տեղեկություններ, ինչպիսիք են, օրինակ, գաղտնաբառերը, վճարային տվյալները, լուսանկարները, հաղորդագրությունները և նվագարկվող աուդիո ֆայլերը։"</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Տեսագրել ամբողջ էկրանը"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Տեսագրել մեկ հավելված"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Երբ դուք տեսագրում եք էկրանը, Android-ին հասանելի է դառնում այն ամենը, ինչ տեսանելի է էկրանին և նվագարկվում է ձեր սարքում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Երբ դուք տեսագրում եք որևէ հավելվածի էկրանը, Android-ին հասանելի է դառնում այն ամենը, ինչ ցուցադրվում է կամ նվագարկվում այդ հավելվածում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Սկսել տեսագրումը"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Ձայնագրել"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Սարքի ձայները"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Ձեր սարքի ձայները, օրինակ՝ երաժշտությունը, զանգերն ու զանգերանգները"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Ձայնագրման և հեռարձակման ընթացքում ծառայությունների մատակարարին հասանելի կլինեն ձեր սարքի էկրանին ցուցադրվող տեղեկությունները և ձեր սարքով նվագարկվող նյութերը։ Սա ներառում է այնպիսի տեղեկություններ, ինչպիսիք են, օրինակ, գաղտնաբառերը, վճարային տվյալները, լուսանկարները, հաղորդագրությունները և նվագարկվող աուդիո ֆայլերը։"</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Սկսե՞լ ձայնագրումը կամ հեռարձակումը"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Սկսե՞լ ձայնագրումը կամ հեռարձակումը <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> հավելվածով"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Թույլատրե՞լ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> հավելվածին ցուցադրել կամ տեսագրել էկրանը"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Ամբողջ էկրանը"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Մեկ հավելված"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք էկրանը, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> հավելվածին հասանելի է դառնում այն ամենը, ինչ տեսանելի է էկրանին և նվագարկվում է ձեր սարքում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք որևէ հավելվածի էկրանը, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> հավելվածին հասանելի է դառնում այն ամենը, ինչ ցուցադրվում է կամ նվագարկվում այդ հավելվածում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Շարունակել"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Հավելվածի էկրանի ցուցադրում կամ տեսագրում"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Մաքրել բոլորը"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Կառավարել"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Պատմություն"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 0156900..e372c37 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notifikasi yang sedang berjalan untuk sesi rekaman layar"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Mulai merekam?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Saat merekam, Sistem Android dapat ikut merekam informasi sensitif yang terlihat di layar atau diputar di perangkat Anda. Informasi ini mencakup sandi, info pembayaran, foto, pesan, dan audio."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Rekam seluruh layar"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Rekam satu aplikasi"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Saat Anda merekam, Android akan memiliki akses ke semua hal yang ditampilkan di layar atau yang diputar di perangkat Anda. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Saat Anda merekam aplikasi, Android akan memiliki akses ke semua hal yang ditampilkan atau yang diputar di aplikasi tersebut. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Mulai merekam"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Rekam audio"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio perangkat"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Suara dari perangkat Anda, seperti musik, panggilan, dan nada dering"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Layanan yang menyediakan fungsi ini akan memiliki akses ke semua informasi yang terlihat di layar atau diputar dari perangkat saat merekam atau melakukan transmisi. Ini mencakup informasi seperti sandi, detail pembayaran, foto, pesan, dan audio yang Anda putar."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Mulai merekam atau melakukan transmisi?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Mulai merekam atau melakukan transmisi dengan <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Izinkan <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> untuk membagikan atau merekam?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Seluruh layar"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Satu aplikasi"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Jika Anda membagikan, merekam, atau mentransmisikan, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> akan memiliki akses ke semua hal yang ditampilkan di layar atau yang diputar di perangkat Anda. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Jika Anda membagikan, merekam, atau mentransmisikan suatu aplikasi, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> akan memiliki akses ke semua hal yang ditampilkan atau yang diputar di aplikasi tersebut. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Lanjutkan"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Bagikan atau rekam aplikasi"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Hapus semua"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Kelola"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Histori"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 7622ad4..136db9e 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Áframhaldandi tilkynning fyrir skjáupptökulotu"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Hefja upptöku?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Á meðan tekið er upp getur Android kerfið fangað viðkvæmar upplýsingar sem sjást á skjánum eða spilast í tækinu. Þar á meðal eru upplýsingar á borð við aðgangsorð, greiðsluupplýsingar, myndir, skilaboð og hljóð."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Taka upp allan skjáinn"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Taka upp eitt forrit"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Þegar þú tekur upp hefur Android aðgang að öllu sem sést á skjánum eða spilast í tækinu. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Þegar þú tekur upp forrit hefur Android aðgang að öllu sem sést eða spilast í viðkomandi forriti. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Hefja upptöku"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Taka upp hljóð"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Hljóð tækis"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Hljóð úr tækinu á borð við tónlist, símtöl og hringitóna"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Þjónustan sem býður upp á þennan eiginleika fær aðgang að öllum upplýsingum sem sjást á skjánum eða eru spilaðar í tækinu á meðan upptaka eða útsending er í gangi, þar á meðal aðgangsorði, greiðsluupplýsingum, myndum, skilaboðum og hljóðefni sem þú spilar."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Viltu hefja upptöku eða útsendingu?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Viltu hefja upptöku eða útsendingu með <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Leyfa <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> að deila eða taka upp?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Allur skjárinn"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Eitt forrit"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Þegar þú deilir, tekur upp eða sendir út hefur<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aðgang að öllu sem sést á skjánum eða spilast í tækinu. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Þegar þú deilir, tekur upp eða sendir út forrit hefur <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aðgang að öllu sem sést eða spilast í viðkomandi forriti. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Áfram"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Deila eða taka upp forrit"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Hreinsa allt"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Stjórna"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Ferill"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index d6631e7..df3e6fd 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Экранды бейнеге жазудың ағымдағы хабарландыруы"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Жазу басталсын ба?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Android жүйесі экранда көрсетілетін немесе құрылғыда ойнатылатын құпия ақпаратты жазып алуы мүмкін. Ондай ақпаратқа құпия сөздер, төлем ақпараты, фотосуреттер, хабарлар және аудио жатады."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Бүкіл экранды жазу"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Жалғыз қолданбаны жазу"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Жазу кезінде Android жүйесі экраныңызда көрінетін не құрылғыңызда ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Қолданба экранын жазу кезінде Android жүйесі қолданбада көрінетін не ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Жазуды бастау"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Аудио жазу"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Құрылғыдан шығатын дыбыс"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Музыка, қоңыраулар және рингтондар сияқты құрылғыдан шығатын дыбыс"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Осы функцияны ұсынатын қызмет жазу не трансляциялау кезінде экранда көрсетілетін немесе құрылғыда дыбысталатын ақпаратты пайдалана алады. Бұған құпия сөздер, төлем туралы мәліметтер, суреттер, хабарлар және аудиоматериалдар кіреді."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Жазу немесе трансляциялау басталсын ба?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> арқылы жазу немесе трансляциялау басталсын ба?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> қолданбасына экранды бөлісуге не жазуға рұқсат берілсін бе?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Бүкіл экран"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Жалғыз қолданба"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Бөлісу, жазу не трансляциялау кезінде <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> қолданбасы экраныңызда көрінетін не құрылғыңызда ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Қолданба экранын бөлісу, жазу не трансляциялау кезінде <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> қолданбасы онда көрінетін не ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Жалғастыру"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Қолданба экранын бөлісу не жазу"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Барлығын тазалау"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Басқару"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Тарих"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 713d20d..83a5e87 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಸೆಶನ್‌ಗಾಗಿ ಚಾಲ್ತಿಯಲ್ಲಿರುವ ಅಧಿಸೂಚನೆ"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭಿಸಬೇಕೆ?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"ರೆಕಾರ್ಡಿಂಗ್ ಸಮಯದಲ್ಲಿ, ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ಗೋಚರಿಸುವ ಅಥವಾ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಮಾಡಲಾದ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯನ್ನು Android ಸಿಸ್ಟಂ ಕ್ಯಾಪ್ಚರ್ ಮಾಡಬಹುದು. ಇದು ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ಮಾಹಿತಿ, ಫೋಟೋಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಆಡಿಯೋವನ್ನು ಒಳಗೊಂಡಿದೆ."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"ಸಂಪೂರ್ಣ ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ಒಂದೇ ಆ್ಯಪ್ ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ನೀವು ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ, ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಕಾಣಿಸುವ ಅಥವಾ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ Android ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ, ಆ ಆ್ಯಪ್‌ನಲ್ಲಿ ತೋರಿಸಲಾಗುವ ಅಥವಾ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ Android ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭಿಸಿ"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"ಆಡಿಯೋ ರೆಕಾರ್ಡ್‌ ಮಾಡಿ"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ಸಾಧನದ ಆಡಿಯೋ"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"ನಿಮ್ಮ ಸಾಧನದ ಧ್ವನಿ ಉದಾ: ಸಂಗೀತ, ಕರೆಗಳು ಮತ್ತು ರಿಂಗ್‌ಟೋನ್‌ಗಳು"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ಈ ವೈಶಿಷ್ಟ್ಯವು ಒದಗಿಸುವ ಸೇವೆಗಳು, ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಗೋಚರಿಸುವ ಅಥವಾ ರೆಕಾರ್ಡಿಂಗ್ ಅಥವಾ ಬಿತ್ತರಿಸುವಾಗ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಆಗುವ ಎಲ್ಲಾ ಮಾಹಿತಿಗಳಿಗೆ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿರುತ್ತವೆ. ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಫೋಟೋಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಆಡಿಯೋ ಪ್ಲೇಬ್ಯಾಕ್‌ನಂತಹ ಮಾಹಿತಿಯನ್ನು ಇದು ಒಳಗೊಂಡಿದೆ."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ರೆಕಾರ್ಡಿಂಗ್ ಅಥವಾ ಬಿತ್ತರಿಸುವಿಕೆಯನ್ನು ಪ್ರಾರಂಭಿಸಬೇಕೆ?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಮೂಲಕ ರೆಕಾರ್ಡಿಂಗ್, ಬಿತ್ತರಿಸುವುದನ್ನು ಪ್ರಾರಂಭಿಸುವುದೇ?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"ಹಂಚಿಕೊಳ್ಳಲು ಅಥವಾ ರೆಕಾರ್ಡ್ ಮಾಡಲು <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಅನ್ನು ಅನುಮತಿಸಬೇಕೆ?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"ಸಂಪೂರ್ಣ ಸ್ಕ್ರೀನ್"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ಒಂದೇ ಆ್ಯಪ್"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"ನೀವು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಬಿತ್ತರಿಸುತ್ತಿರುವಾಗ, ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಕಾಣಿಸುವ ಅಥವಾ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಬಿತ್ತರಿಸುತ್ತಿರುವಾಗ, ಆ ಆ್ಯಪ್‌ನಲ್ಲಿ ತೋರಿಸಲಾಗುವ ಅಥವಾ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ಮುಂದುವರಿಸಿ"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ಆ್ಯಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳಿ ಅಥವಾ ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ನಿರ್ವಹಿಸಿ"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ಇತಿಹಾಸ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 290c517..f3fb7a0 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"화면 녹화 세션에 관한 지속적인 알림"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"녹화를 시작하시겠습니까?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Android 시스템이 녹화 중에 화면에 표시되거나 기기에서 재생되는 민감한 정보를 캡처할 수 있습니다. 여기에는 비밀번호, 결제 정보, 사진, 메시지 및 오디오가 포함됩니다."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"전체 화면 녹화"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"단일 앱 녹화"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"녹화할 때 Android에서 화면에 표시되거나 기기에서 재생되는 모든 항목에 액세스할 수 있습니다. 따라서 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"앱을 녹화할 때 Android에서 해당 앱에서 표시되거나 재생되는 모든 항목에 액세스할 수 있으므로 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"녹화 시작"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"오디오 녹음"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"기기 오디오"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"음악, 통화, 벨소리와 같이 기기에서 나는 소리"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"이 기능을 제공하는 서비스는 녹화 또는 전송 중에 화면에 표시되거나 기기에서 재생되는 모든 정보에 액세스할 수 있습니다. 여기에는 비밀번호, 결제 세부정보, 사진, 메시지, 재생하는 오디오 같은 정보가 포함됩니다."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"녹화 또는 전송을 시작하시겠습니까?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>으로 녹화 또는 전송을 시작하시겠습니까?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>에서 공유 또는 녹화를 허용할까요?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"전체 화면"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"단일 앱"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"공유하거나 녹화하거나 전송할 때 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 앱에서 화면에 표시되거나 기기에서 재생되는 모든 항목에 액세스할 수 있습니다. 따라서 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"앱을 공유하거나 녹화하거나 전송할 때는 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>에서 해당 앱에 표시되거나 재생되는 모든 항목에 액세스할 수 있으므로 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"계속"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"앱 공유 또는 녹화"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"모두 지우기"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"관리"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"기록"</string>
diff --git a/packages/SystemUI/res/values-land/styles.xml b/packages/SystemUI/res/values-land/styles.xml
index 8919198..aefd998 100644
--- a/packages/SystemUI/res/values-land/styles.xml
+++ b/packages/SystemUI/res/values-land/styles.xml
@@ -18,4 +18,42 @@
     <style name="BrightnessDialogContainer" parent="@style/BaseBrightnessDialogContainer">
         <item name="android:layout_width">360dp</item>
     </style>
+
+    <style name="AuthCredentialHeaderStyle">
+        <item name="android:paddingStart">48dp</item>
+        <item name="android:paddingEnd">24dp</item>
+        <item name="android:paddingTop">48dp</item>
+        <item name="android:paddingBottom">10dp</item>
+        <item name="android:gravity">top|left</item>
+    </style>
+
+    <style name="AuthCredentialPatternContainerStyle">
+        <item name="android:gravity">center</item>
+        <item name="android:maxHeight">320dp</item>
+        <item name="android:maxWidth">320dp</item>
+        <item name="android:minHeight">200dp</item>
+        <item name="android:minWidth">200dp</item>
+        <item name="android:paddingHorizontal">60dp</item>
+        <item name="android:paddingVertical">20dp</item>
+    </style>
+
+    <style name="TextAppearance.AuthNonBioCredential.Title">
+        <item name="android:fontFamily">google-sans</item>
+        <item name="android:layout_marginTop">6dp</item>
+        <item name="android:textSize">36dp</item>
+        <item name="android:focusable">true</item>
+    </style>
+
+    <style name="TextAppearance.AuthNonBioCredential.Subtitle">
+        <item name="android:fontFamily">google-sans</item>
+        <item name="android:layout_marginTop">6dp</item>
+        <item name="android:textSize">18sp</item>
+    </style>
+
+    <style name="TextAppearance.AuthNonBioCredential.Description">
+        <item name="android:fontFamily">google-sans</item>
+        <item name="android:layout_marginTop">6dp</item>
+        <item name="android:textSize">18sp</item>
+    </style>
+
 </resources>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 9cb8bfd..5a63b62 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Aktīvs paziņojums par ekrāna ierakstīšanas sesiju"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Vai sākt ierakstīšanu?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Ierakstīšanas laikā Android sistēmā var tikt tverta jebkura sensitīvā informācija, kas ir redzama jūsu ekrānā vai tiek atskaņota jūsu ierīcē. Šī informācija ir paroles, maksājumu informācija, fotoattēli, ziņojumi un audio."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Ierakstīt visu ekrānu"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Ierakstīt vienu lietotni"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Ierakstīšanas laikā Android var piekļūt visam, kas tiek rādīts jūsu ekrānā vai atskaņots jūsu ierīcē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Lietotnes ierakstīšanas laikā Android var piekļūt visam, kas tiek rādīts vai atskaņots attiecīgajā lietotnē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Sākt ierakstīšanu"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Ierakstīt audio"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Ierīces audio"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Skaņa no jūsu ierīces, piemēram, mūzika, sarunas un zvana signāli"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Pakalpojums, kas nodrošina šo funkciju, iegūs piekļuvi visai informācijai, kas ierakstīšanas vai apraides laikā tiks rādīta jūsu ekrānā vai atskaņota jūsu ierīcē. Atļauja attiecas uz tādu informāciju kā paroles, maksājumu informācija, fotoattēli, ziņojumi un jūsu atskaņotais audio saturs."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vai vēlaties sākt ierakstīšanu/apraidi?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Vai vēlaties sākt ierakstīšanu vai apraidi, izmantojot lietotni <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vai atļaujat lietotnei <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> veikt kopīgošanu vai ierakstīšanu?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Viss ekrāns"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Viena lietotne"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Kopīgošanas, ierakstīšanas vai apraides laikā <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> var piekļūt visam, kas tiek rādīts jūsu ekrānā vai atskaņots jūsu ierīcē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Lietotnes kopīgošanas, ierakstīšanas vai apraides laikā <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> var piekļūt visam, kas tiek rādīts vai atskaņots attiecīgajā lietotnē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Turpināt"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Lietotnes kopīgošana vai ierakstīšana"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Dzēst visu"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Pārvaldīt"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Vēsture"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index bf1c262..7611822 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"ഒരു സ്ക്രീൻ റെക്കോർഡിംഗ് സെഷനായി നിലവിലുള്ള അറിയിപ്പ്"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"റെക്കോർഡിംഗ് ആരംഭിക്കണോ?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"റെക്കോർഡ് ചെയ്യുമ്പോൾ, നിങ്ങളുടെ സ്‌ക്രീനിൽ ദൃശ്യമാകുന്നതോ ഉപകരണത്തിൽ പ്ലേ ചെയ്യുന്നതോ ആയ ഏത് തന്ത്രപ്രധാന വിവരങ്ങളും Android സിസ്റ്റത്തിന് പകർത്താനാവും. പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിവരം, ഫോട്ടോകൾ, സന്ദേശങ്ങൾ, ഓഡിയോ എന്നിവ ഇതിൽ ഉൾപ്പെടുന്നു."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"പൂർണ സ്ക്രീൻ റെക്കോർഡ് ചെയ്യൂ"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ഒറ്റ ആപ്പ് റെക്കോർഡ് ചെയ്യുക"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"റെക്കോർഡ് ചെയ്യുമ്പോൾ, Android-ന് സ്ക്രീനിൽ ദൃശ്യമാകുന്നതോ ഉപകരണത്തിൽ പ്ലേ ചെയ്യുന്നതോ ആയ ഏത് കാര്യത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്‌മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ഒരു ആപ്പ് റെക്കോർഡ് ചെയ്യുമ്പോൾ, Android-ന് ആ ആപ്പിൽ കാണിക്കുന്നതോ പ്ലേ ചെയ്യുന്നതോ ആയ എല്ലാത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്‌മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"റെക്കോർഡിംഗ് ആരംഭിക്കുക"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"ഓഡിയോ റെക്കോർഡ് ചെയ്യുക"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ഉപകരണത്തിന്റെ ഓഡിയോ"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"സംഗീതം, കോളുകൾ, റിംഗ്‌ടോണുകൾ എന്നിവപോലെ നിങ്ങളുടെ ഉപകരണത്തിൽ നിന്നുള്ള ശബ്ദം"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"റെക്കോർഡ് ചെയ്യുമ്പോഴോ കാസ്‌റ്റ് ചെയ്യുമ്പോഴോ നിങ്ങളുടെ ഉപകരണത്തിൽ നിന്ന് പ്ലേ ചെയ്യുന്നതോ നിങ്ങളുടെ സ്‌ക്രീനിൽ ദൃശ്യമാകുന്നതോ ആയ എല്ലാ വിവരങ്ങളിലേക്കും ഈ ഫംഗ്‌ഷൻ ലഭ്യമാക്കുന്ന സേവനത്തിന് ആക്‌സസ് ഉണ്ടായിരിക്കും. നിങ്ങൾ പ്ലേ ചെയ്യുന്ന ഓഡിയോ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, പാസ്‌വേഡുകൾ എന്നിവ പോലുള്ള വിവരങ്ങൾ ഇതിൽ ഉൾപ്പെടുന്നു."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"റെക്കോർഡ് ചെയ്യൽ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യൽ ആരംഭിക്കണോ?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ഉപയോഗിച്ച് റെക്കോർഡ് ചെയ്യൽ അല്ലെങ്കിൽ കാസ്‌റ്റ് ചെയ്യൽ ആരംഭിക്കണോ?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"പങ്കിടാനോ റെക്കോർഡ് ചെയ്യാനോ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> എന്നതിനെ അനുവദിക്കണോ?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"മുഴുവൻ സ്‌ക്രീൻ"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ഒറ്റ ആപ്പ്"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> എന്നതിന് നിങ്ങളുടെ സ്ക്രീനിൽ ദൃശ്യമാകുന്നതോ ഉപകരണത്തിൽ പ്ലേ ചെയ്യുന്നതോ ആയ ഏത് കാര്യത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്‌മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ഒരു ആപ്പ് പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> എന്നതിന് ആപ്പിൽ കാണിക്കുന്ന അല്ലെങ്കിൽ പ്ലേ ചെയ്യുന്ന എല്ലാത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്‌മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"തുടരുക"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ഒരു ആപ്പ് പങ്കിടുക അല്ലെങ്കിൽ റെക്കോർഡ് ചെയ്യുക"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"എല്ലാം മായ്‌ക്കുക"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"മാനേജ് ചെയ്യുക"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ചരിത്രം"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index f7b8ef0..2f96f33 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"स्क्रीन रेकॉर्ड सत्रासाठी सुरू असलेली सूचना"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"रेकॉर्डिंग सुरू करायचे आहे का?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"रेकॉर्डिंग करताना, Android सिस्टीम तुमच्या स्क्रीनवर दिसणारी किंवा तुमच्या डिव्हाइसवर प्ले केलेली कोणतीही संवेदनशील माहिती कॅप्चर करू शकते. यात पासवर्ड, पेमेंट माहिती, फोटो, मेसेज आणि ऑडिओचा समावेश आहे."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"संपूर्ण स्क्रीन रेकॉर्ड करा"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"एकच अ‍ॅप रेकॉर्ड करा"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"तुम्ही रेकॉर्ड करत असताना, Android ला तुमच्या स्क्रीनवर दाखवलेल्या किंवा डिव्हाइसवर प्ले केलेल्या कोणत्याही गोष्टीचा अ‍ॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज किंवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"तुम्ही अ‍ॅप रेकॉर्ड करत असताना, Android ला त्या अ‍ॅपवर दाखवलेल्या किंवा प्ले केलेल्या कोणत्याही गोष्टीचा अ‍ॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज किंवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"रेकॉर्डिंग सुरू करा"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"ऑडिओ रेकॉर्ड करा"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"डिव्हाइस ऑडिओ"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"तुमच्या डिव्हाइसवरील आवाज, जसे की संगीत, कॉल आणि रिंगटोन"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"हे कार्य पुरवणाऱ्या सेवेस तुमच्या स्क्रीनवर दृश्यमान असलेल्या किंवा रेकॉर्ड किंवा कास्ट करताना तुमच्या डिव्हाइसमधून प्ले केलेल्या सर्व माहितीचा अ‍ॅक्सेस असेल. यामध्ये पासवर्ड, पेमेंट तपशील, फोटो, मेसेज आणि तुम्ही प्ले केलेला ऑडिओ यासारख्या माहितीचा समावेश असतो."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"रेकॉर्ड करणे किंवा कास्ट करणे सुरू करायचे का ?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ने रेकॉर्ड करणे किंवा कास्ट करणे सुरू करायचे का?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ला शेअर किंवा रेकॉर्ड करण्याची अनुमती द्यायची आहे का?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"संपूर्ण स्क्रीन"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"एक अ‍ॅप"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"तुम्ही शेअर, रेकॉर्ड किंवा कास्ट करत असताना, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ला तुमच्या स्क्रीनवर दाखवलेल्या किंवा डिव्हाइसवर प्ले केलेल्या कोणत्याही गोष्टीचा अ‍ॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज किंवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"तुम्ही अ‍ॅप शेअर, रेकॉर्ड किंवा कास्ट करत असताना, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ला त्या अ‍ॅपवर दाखवलेल्या किंवा प्ले केलेल्या कोणत्याही गोष्टीचा अ‍ॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज किंवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"पुढे सुरू ठेवा"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"अ‍ॅप शेअर किंवा रेकॉर्ड करा"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"सर्व साफ करा"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"व्यवस्थापित करा"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index a874e45..fe1b908 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"ဖန်သားပြင် ရိုက်ကူးသည့် စက်ရှင်အတွက် ဆက်တိုက်လာနေသော အကြောင်းကြားချက်"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"စတင် ရိုက်ကူးမလား။"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"ရိုက်ကူးနေစဉ်အတွင်း Android စနစ်သည် သင့်ဖန်သားပြင်ပေါ်တွင် မြင်နိုင်သော (သို့) သင့်စက်ပစ္စည်းတွင် ဖွင့်ထားသော အရေးကြီးသည့် အချက်အလက်များကို ရိုက်ယူနိုင်သည်။ ၎င်းတွင် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ ဓာတ်ပုံ၊ မက်ဆေ့ဂျ်နှင့် အသံများ ပါဝင်သည်။"</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"ဖန်သားပြင်တစ်ခုလုံးရိုက်ကူးရန်"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"အက်ပ်တစ်ခုတွင် ရိုက်ကူးရန်"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ရိုက်ကူးနေစဉ် Android သည် သင့်ဖန်သားပြင်ရှိ မြင်နိုင်သည့် (သို့) သင့်စက်တွင် ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားအရေးကြီးအချက်အလက်များနှင့်ပတ်သက်၍ ဂရုစိုက်ပါ။"</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"အက်ပ်တစ်ခုကို ရိုက်ကူးနေစဉ် Android သည် ၎င်းအက်ပ်တွင် ပြထားသည့် (သို့) ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားအရေးကြီးအချက်အလက်များနှင့်ပတ်သက်၍ ဂရုစိုက်ပါ။"</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"စတင်ရိုက်ကူးရန်"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"အသံဖမ်းရန်"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"စက်ပစ္စည်းအသံ"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"သီချင်း၊ ဖုန်းခေါ်ဆိုမှုနှင့် ဖုန်းမြည်သံကဲ့သို့ သင့်စက်ပစ္စည်းမှ အသံ"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ဤဝန်ဆောင်မှုသည် ရိုက်ကူးဖမ်းယူနေစဉ် (သို့) ကာစ်လုပ်နေစဉ်အတွင်း သင့်ဖန်သားပြင်တွင် မြင်ရသော (သို့) သင့်စက်တွင် ဖွင့်ထားသော အချက်အလက်အားလုံးကို ကြည့်နိုင်ပါမည်။ ၎င်းတွင် စကားဝှက်များ၊ ငွေပေးချေမှုအသေးစိတ်များ၊ ဓာတ်ပုံများ၊ မက်ဆေ့ဂျ်များနှင့် သင်ဖွင့်သည့်အသံကဲ့သို့သော အချက်အလက်များ ပါဝင်သည်။"</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ရိုက်ကူးဖမ်းယူခြင်း (သို့) ကာစ်လုပ်ခြင်း စတင်မလား။"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> နှင့် ဖမ်းယူခြင်း သို့မဟုတ် ကာစ်လုပ်ခြင်း စတင်မလား။"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"မျှဝေရန် (သို့) ရိုက်ကူးရန် <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ကို ခွင့်ပြုမလား။"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"ဖန်သားပြင်တစ်ခုလုံး"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"အက်ပ်တစ်ခုတွင်"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"မျှဝေ၊ ရိုက်ကူး (သို့) ကာစ်လုပ်သည့်အခါ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> သည် သင့်ဖန်သားပြင်ရှိ မြင်နိုင်သည့် (သို့) သင့်စက်တွင် ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားအရေးကြီးအချက်အလက်များနှင့်ပတ်သက်၍ ဂရုစိုက်ပါ။"</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"အက်ပ်ဖြင့် မျှဝေ၊ ရိုက်ကူး (သို့) ကာစ်လုပ်သည့်အခါ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> သည် ၎င်းအက်ပ်တွင် ပြထားသည့် (သို့) ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားအရေးကြီးအချက်အလက်များနှင့်ပတ်သက်၍ ဂရုစိုက်ပါ။"</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ရှေ့ဆက်ရန်"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"အက်ပ် မျှဝေခြင်း (သို့) ရိုက်ကူးခြင်း"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"အားလုံးရှင်းရန်"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"စီမံရန်"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"မှတ်တမ်း"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 6dd9d8f..951eb8c 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Vedvarende varsel for et skjermopptak"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Vil du starte et opptak?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Under opptak kan Android-systemet registrere all sensitiv informasjon som er synlig på skjermen eller spilles av på enheten. Dette inkluderer passord, betalingsinformasjon, bilder, meldinger og lyd."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Ta opp hele skjermen"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Ta opp én app"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Når du tar opp noe, har Android tilgang til alt som vises på skjermen eller spilles av på enheten. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Når du tar opp en app, har Android tilgang til alt som vises eller spilles av i appen. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Start opptaket"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Spill inn lyd"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Enhetslyd"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Lyd fra enheten, f.eks. musikk, samtaler og ringelyder"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Tjenesten som leverer denne funksjonen, får tilgang til all informasjon som er synlig på skjermen din, eller som spilles av fra enheten når du tar opp eller caster. Dette inkluderer informasjon som passord, betalingsopplysninger, bilder, meldinger og lyd du spiller av."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vil du starte opptak eller casting?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Vil du starte opptak eller casting med <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vil du gi <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tillatelse til å dele eller ta opp?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Hele skjermen"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Én app"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Når du deler, tar opp eller caster noe, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tilgang til alt som vises på skjermen eller spilles av på enheten. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Når du deler, tar opp eller caster en app, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tilgang til alt som vises eller spilles av i den aktuelle appen. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Fortsett"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Del eller ta opp en app"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Fjern alt"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Administrer"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Logg"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 2362b40..35112a7 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"कुनै स्क्रिन रेकर्ड गर्ने सत्रका लागि चलिरहेको सूचना"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"रेकर्ड गर्न थाल्ने हो?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"रेकर्ड गर्दा, Android सिस्टमले तपाईंको स्क्रिनमा देखिने वा तपाईंको डिभाइसमा प्ले गरिने सबै संवेदनशील जानकारी रेकर्ड गर्न सक्छ। यो जानकारीमा पासवर्ड, भुक्तानीसम्बन्धी जानकारी, फोटो, सन्देश र अडियो समावेश हुन्छ।"</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"पूरै स्क्रिन रेकर्ड गर्नुहोस्"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"एउटा एप मात्र रेकर्ड गर्नुहोस्"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"तपाईंले रेकर्ड गर्दा Android ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले रेकर्ड गर्दा पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"तपाईंले रेकर्ड गर्दा Android ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले रेकर्ड गर्दा पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"रेकर्ड गर्न थाल्नुहोस्"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"अडियो रेकर्ड गरियोस्"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"डिभाइसको अडियो"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"तपाईंको डिभाइसका सङ्गीत, कल र रिङटोन जस्ता साउन्ड"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"यो कार्य गर्ने सेवाले तपाईंको स्क्रिनमा देख्न सकिने सबै जानकारी अथवा रेकर्ड वा कास्ट गर्दा तपाईंको डिभाइसबाट प्ले गरिएका कुरा हेर्न तथा प्रयोग गर्न सक्छ। यसले हेर्न तथा प्रयोग गर्न सक्ने कुरामा पासवर्ड, भुक्तानीका विवरण, फोटो, सन्देश र तपाईंले प्ले गर्ने अडियो कुराहरू समावेश हुन सक्छन्।"</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"रेकर्ड गर्न वा cast गर्न थाल्ने हो?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> मार्फत रेकर्ड गर्न वा cast गर्न थाल्ने हो?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> लाई सेयर गर्न वा रेकर्ड गर्न दिने हो?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"पूर्ण स्क्रिन"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"एकल एप"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले सेयर, रेकर्ड वा कास्ट गर्दा पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"जारी राख्नुहोस्"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"सेयर वा रेकर्ड गर्नका लागि एप चयन गर्नुहोस्"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"सबै हटाउनुहोस्"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"व्यवस्थित गर्नुहोस्"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 01d0d9f..04c3774 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"ଏକ ସ୍କ୍ରି‍ନ୍‍ ରେକର୍ଡ୍‍ ସେସନ୍‍ ପାଇଁ ଚାଲୁଥିବା ବିଜ୍ଞପ୍ତି"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"ରେକର୍ଡିଂ ଆରମ୍ଭ କରିବେ?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"ରେକର୍ଡିଂ ସମୟରେ, Android ସିଷ୍ଟମ୍ ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଦେଖାଯାଉଥିବା ବା ଆପଣଙ୍କ ଡିଭାଇସରେ ଚାଲୁଥିବା ଯେ କୌଣସି ସମ୍ବେଦନଶୀଳ ସୂଚନାକୁ କ୍ୟାପଚର୍ କରିପାରିବ। ଏଥିରେ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ସୂଚନା, ଫଟୋ, ମେସେଜ ଏବଂ ଅଡିଓ ଅନ୍ତର୍ଭୁକ୍ତ।"</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"ସମ୍ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନ ରେକର୍ଡ କର"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ଏକ ସିଙ୍ଗଲ ଆପ ରେକର୍ଡ କରନ୍ତୁ"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ଆପଣ ରେକର୍ଡିଂ କରିବା ବେଳେ, ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ଆପଣଙ୍କ ଡିଭାଇସରେ ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ Androidର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ଆପଣ ଏକ ଆପ ରେକର୍ଡିଂ କରିବା ବେଳେ, ସେହି ଆପରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ Androidର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"ରେକର୍ଡିଂ ଆରମ୍ଭ କରନ୍ତୁ"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"ଅଡିଓ ରେକର୍ଡ କରନ୍ତୁ"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ଡିଭାଇସ୍ ଅଡିଓ"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"ଆପଣଙ୍କ ଡିଭାଇସରୁ ସାଉଣ୍ଡ, ଯେପରିକି ସଙ୍ଗୀତ, କଲ୍ ଏବଂ ରିଂଟୋନଗୁଡ଼ିକ"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ରେକର୍ଡିଂ ବା କାଷ୍ଟିଂ ବେଳେ ଆପଣଙ୍କର ଡିଭାଇସରେ ଦେଖାଯାଉଥିବା ବା ଆପଣଙ୍କ ଡିଭାଇସରୁ ପ୍ଲେ କରାଯାଉଥିବା ସବୁ ସୂଚନାକୁ ଏହି ଫଙ୍କସନ୍ ପ୍ରଦାନ କରୁଥିବା ସେବାର ଆକ୍ସେସ୍ ରହିବ। ପାସ୍‌ୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ଫଟୋ, ମେସେଜ୍ ଏବଂ ଆପଣ ଚଲାଉଥିବା ଅଡିଓ ପରି ସୂଚନା ଏଥିରେ ଅନ୍ତର୍ଭୁକ୍ତ ଅଛି।"</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ରେକର୍ଡିଂ ବା କାଷ୍ଟିଂ ଆରମ୍ଭ କରିବେ?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ସହ ରେକର୍ଡିଂ ବା କାଷ୍ଟିଂ ଆରମ୍ଭ କରିବେ?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"ସେୟାର କିମ୍ବା ରେକର୍ଡ କରିବା ପାଇଁ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"ସମ୍ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନ"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ଏକ ସିଙ୍ଗଲ ଆପ"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"ଆପଣ ସେୟାର, ରେକର୍ଡ କିମ୍ବା କାଷ୍ଟ କରିବା ସମୟରେ, ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ଆପଣଙ୍କ ଡିଭାଇସରେ ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ଆପଣ ସେୟାର, ରେକର୍ଡ କିମ୍ବା କାଷ୍ଟ କରିବା ସମୟରେ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ଆପରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ ସେହି ଆପର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ଜାରି ରଖନ୍ତୁ"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ଏକ ଆପକୁ ସେୟାର କିମ୍ବା ରେକର୍ଡ କରନ୍ତୁ"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ସମସ୍ତ ଖାଲି କରନ୍ତୁ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ପରିଚାଳନା କରନ୍ତୁ"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ଇତିହାସ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index e9f4cf9..5f9709a 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"ਕਿਸੇ ਸਕ੍ਰੀਨ ਰਿਕਾਰਡ ਸੈਸ਼ਨ ਲਈ ਚੱਲ ਰਹੀ ਸੂਚਨਾ"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"ਕੀ ਰਿਕਾਰਡਿੰਗ ਸ਼ੁਰੂ ਕਰਨੀ ਹੈ?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"ਰਿਕਾਰਡਿੰਗ ਕਰਨ ਵੇਲੇ, Android ਸਿਸਟਮ ਕੋਈ ਵੀ ਅਜਿਹੀ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਕੈਪਚਰ ਕਰ ਸਕਦਾ ਹੈ ਜੋ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਖਣਯੋਗ ਹੈ ਜਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਜਾਂਦੀ ਹੈ। ਇਸ ਵਿੱਚ ਪਾਸਵਰਡ, ਭੁਗਤਾਨ ਵੇਰਵੇ, ਫ਼ੋਟੋਆਂ, ਸੁਨੇਹੇ ਅਤੇ ਆਡੀਓ ਸ਼ਾਮਲ ਹਨ।"</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"ਪੂਰੀ ਸਕ੍ਰੀਨ ਨੂੰ ਰਿਕਾਰਡ ਕਰੋ"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ਇਕਹਿਰੀ ਐਪ ਨੂੰ ਰਿਕਾਰਡ ਕਰੋ"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਰਿਕਾਰਡਿੰਗ ਕਰਨ ਵੇਲੇ, Android ਕੋਲ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਸਦੀ ਜਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਰਿਕਾਰਡਿੰਗ ਕਰਨ ਵੇਲੇ, Android ਕੋਲ ਉਸ ਐਪ \'ਤੇ ਦਿਖਾਈ ਗਈ ਜਾਂ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"ਰਿਕਾਰਡਿੰਗ ਸ਼ੁਰੂ ਕਰੋ"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"ਆਡੀਓ ਰਿਕਾਰਡ ਕਰੋ"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ਡੀਵਾਈਸ ਆਡੀਓ"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀ ਧੁਨੀ, ਜਿਵੇਂ ਕਿ ਸੰਗੀਤ, ਕਾਲਾਂ ਅਤੇ ਰਿੰਗਟੋਨਾਂ"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ਇਹ ਫੰਕਸ਼ਨ ਪ੍ਰਦਾਨ ਕਰਨ ਵਾਲੀ ਸੇਵਾ ਕੋਲ ਸਾਰੀ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਹੋਵੇਗੀ ਜੋ ਕਿ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਖਣਯੋਗ ਹੁੰਦੀ ਹੈ ਜਾਂ ਰਿਕਾਰਡ ਜਾਂ ਕਾਸਟ ਕਰਨ ਵੇਲੇ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਜਾਂਦੀ ਹੈ। ਇਸ ਵਿੱਚ ਪਾਸਵਰਡ, ਭੁਗਤਾਨ ਵੇਰਵੇ, ਫ਼ੋਟੋਆਂ, ਸੁਨੇਹੇ ਅਤੇ ਤੁਹਾਡੇ ਵੱਲੋਂ ਚਲਾਏ ਆਡੀਓ ਦੀ ਜਾਣਕਾਰੀ ਸ਼ਾਮਲ ਹੁੰਦੀ ਹੈ।"</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ਕੀ ਰਿਕਾਰਡ ਜਾਂ ਕਾਸਟ ਕਰਨਾ ਸ਼ੁਰੂ ਕਰਨਾ ਹੈ?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ਨਾਲ ਰਿਕਾਰਡਿੰਗ ਜਾਂ ਕਾਸਟ ਕਰਨਾ ਸ਼ੁਰੂ ਕਰਨਾ ਹੈ?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"ਕੀ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ਨੂੰ ਸਾਂਝਾ ਕਰਨ ਜਾਂ ਰਿਕਾਰਡ ਕਰਨ ਲਈ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"ਪੂਰੀ ਸਕ੍ਰੀਨ"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ਇਕਹਿਰੀ ਐਪ"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ਕੋਲ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਸਦੀ ਜਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ਕੋਲ ਉਸ ਐਪ \'ਤੇ ਦਿਖਾਈ ਗਈ ਜਾਂ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ਜਾਰੀ ਰੱਖੋ"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ਐਪ ਨੂੰ ਸਾਂਝਾ ਕਰੋ ਜਾਂ ਰਿਕਾਰਡ ਕਰੋ"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ਸਭ ਕਲੀਅਰ ਕਰੋ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ਇਤਿਹਾਸ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index dad40e1..563f8dc 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Stałe powiadomienie o sesji rejestrowania zawartości ekranu"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Rozpocząć nagrywanie?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Podczas nagrywania system Android może rejestrować wszelkie informacje poufne wyświetlane na ekranie lub odtwarzane na urządzeniu. Dotyczy to m.in. haseł, szczegółów płatności, zdjęć, wiadomości i odtwarzanych dźwięków."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Nagrywaj cały ekran"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Nagrywaj pojedynczą aplikację"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Podczas nagrywania Android ma dostęp do wszystkiego, co jest widoczne na ekranie lub odtwarzane na urządzeniu. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Podczas nagrywania treści z aplikacji Android ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Zacznij nagrywać"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Nagraj dźwięk"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Dźwięki z urządzenia"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Dźwięki odtwarzane na urządzeniu, na przykład muzyka, połączenia i dzwonki"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Podczas nagrywania i przesyłania usługa udostępniająca tę funkcję będzie miała dostęp do wszystkich informacji widocznych na ekranie lub odtwarzanych na urządzeniu. Dotyczy to m.in. haseł, szczegółów płatności, zdjęć, wiadomości i odtwarzanych dźwięków."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Rozpocząć nagrywanie lub przesyłanie?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Rozpocząć nagrywanie lub przesyłanie za pomocą aplikacji <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Zezwolić aplikacji <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> na udostępnianie lub nagrywanie?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Cały ekran"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Pojedyncza aplikacja"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Podczas udostępniania, nagrywania lub przesyłania treści aplikacja <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ma dostęp do wszystkiego, co jest widoczne na ekranie lub odtwarzane na urządzeniu. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Podczas udostępniania, nagrywania lub przesyłania treści aplikacja <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Dalej"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Udostępnianie i nagrywanie za pomocą aplikacji"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Usuń wszystkie"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Zarządzaj"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 9110aa0..085522a 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificação persistente de uma sessão de gravação de ecrã"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Iniciar a gravação?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Enquanto estiver a gravar, o sistema Android pode capturar quaisquer informações confidenciais que estejam visíveis no ecrã ou que sejam reproduzidas no dispositivo. Isto inclui palavras-passe, informações de pagamento, fotos, mensagens e áudio."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Gravar o ecrã inteiro"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Gravar só uma app"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Enquanto está a gravar, o Android tem acesso a tudo o que está visível no seu ecrã ou é reproduzido no seu dispositivo. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Enquanto está a gravar uma app, o Android tem acesso a tudo o que é apresentado ou reproduzido nessa app. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Começar gravação"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Gravar áudio"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Áudio do dispositivo"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"O som do dispositivo, como música, chamadas e toques."</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"O serviço que fornece esta função terá acesso a todas as informações que estiverem visíveis no ecrã ou que forem reproduzidas a partir do dispositivo durante a gravação ou transmissão. Isto inclui informações como palavras-passe, detalhes de pagamentos, fotos, mensagens e áudio reproduzido."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Começar a gravar ou a transmitir?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Começar a gravar ou a transmitir com a app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Permitir que a app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> partilhe ou grave?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Ecrã inteiro"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Só uma app"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Quando está a partilhar, gravar ou transmitir, a app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tem acesso a tudo o que está visível no seu ecrã ou é reproduzido no seu dispositivo. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quando está a partilhar, gravar ou transmitir uma app, a app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tem acesso a tudo o que é apresentado ou reproduzido nessa app. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partilhe ou grave uma app"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gerir"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Histórico"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index f82954e..0db2dd5 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificare în curs pentru o sesiune de înregistrare a ecranului"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Începi înregistrarea?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"În timpul înregistrării, sistemul Android poate captura informațiile sensibile vizibile pe ecran sau redate pe dispozitiv. Aici sunt incluse parole, informații de plată, fotografii, mesaje și conținut audio."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Înregistrează tot ecranul"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Înregistrează doar o aplicație"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Când înregistrezi, Android are acces la orice este vizibil pe ecran sau se redă pe dispozitiv. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Când înregistrezi o aplicație, Android are acces la orice se afișează sau se redă în aplicație. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Începe înregistrarea"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Înregistrează audio"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Conținutul audio de la dispozitiv"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sunetul de la dispozitiv, precum muzică, apeluri și tonuri de sonerie"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Serviciul care oferă această funcție va avea acces la toate informațiile vizibile pe ecran sau redate pe dispozitiv în timp ce înregistrezi sau proiectezi. Între aceste informații se numără parole, detalii de plată, fotografii, mesaje și conținutul audio pe care îl redai."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Începi să înregistrezi sau să proiectezi?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Începi să înregistrezi sau să proiectezi cu <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> poate permite accesul sau înregistra?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Tot ecranul"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"O singură aplicație"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Când permiți accesul, înregistrezi sau proiectezi, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> are acces la orice este vizibil pe ecran sau se redă pe dispozitiv. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Când permiți accesul, înregistrezi sau proiectezi o aplicație, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> are acces la orice se afișează pe ecran sau se redă în aplicație. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuă"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Permite accesul la o aplicație sau înregistreaz-o"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Șterge toate notificările"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gestionează"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Istoric"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 84b5061..7dcf873 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Текущее уведомление для записи видео с экрана"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Начать запись?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"В записи может появиться конфиденциальная информация, которая видна на экране или воспроизводится на устройстве, например пароли, сведения о платежах, фотографии, сообщения и аудиозаписи."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Записывать весь экран"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Записывать окно приложения"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Когда вы записываете видео с экрана, Android получает доступ ко всему, что видно и воспроизводится на экране устройства. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Когда вы записываете видео с окна приложения, Android получает доступ ко всему, что видно и воспроизводится в приложении. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Начать запись"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Записывать аудио"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Звук с устройства"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Звук с вашего устройства, например музыка, звонки и рингтоны"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Во время записи или трансляции у сервиса, предоставляющего эту функцию, будет доступ ко всей информации, которая видна на экране или воспроизводится на устройстве, включая пароли, сведения о платежах, фотографии, сообщения и звуки."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Начать запись или трансляцию?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Начать запись или трансляцию через приложение \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\"?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Разрешить приложению \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" демонстрировать экран или записывать видео с него?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Весь экран"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Отдельное приложение"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Когда вы демонстрируете, транслируете экран или записываете видео с него, приложение \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" получает доступ ко всему, что видно и воспроизводится на экране устройства. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Когда вы демонстрируете, транслируете экран или записываете видео с него, приложение \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" получает доступ ко всему, что видно и воспроизводится на экране устройства. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Далее"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Демонстрация экрана или запись видео с него"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Очистить все"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Настроить"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"История"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 1ccdbf3..de498cb 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"තිර පටිගත කිරීමේ සැසියක් සඳහා කෙරෙන දැනුම් දීම"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"පටිගත කිරීම ආරම්භ කරන්නද?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"පටිගත කරන අතරතුර, Android පද්ධතියට ඔබේ තිරයේ පෙනෙන හෝ ඔබේ උපාංගයේ වාදනය කරන ඕනෑම සංවේදී තොරතුරක් ග්‍රහණය කර ගැනීමට හැකිය. මෙයට මුරපද, ගෙවීම් තොරතුරු, ඡායාරූප, පණිවිඩ සහ ඕඩියෝ ඇතුළත් වේ."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"සම්පූර්ණ තිරය පටිගත කරන්න"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"තනි යෙදුමක් පටිගත කරන්න"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ඔබ පටිගත කරන අතරේ, Android හට ඔබේ තිරයේ පෙනෙන හෝ ඔබේ උපාංගයේ වාදනය වන ඕනෑම දෙයකට ප්‍රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්‍රවේශම් වන්න."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ඔබ යෙදුමක් පටිගත කරන අතරේ, Android හට එම යෙදුමේ පෙන්වන හෝ වාදනය කරන ඕනෑම දෙයකට ප්‍රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්‍රවේශම් වන්න."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"පටිගත කිරීම අරඹන්න"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"ඕඩියෝ පටිගත කරන්න"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"උපාංග ඕඩියෝ"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"සංගීතය, ඇමතුම් සහ නාද රිද්ම වැනි ඔබේ උපාංගය වෙතින් ශබ්ද"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"මෙම ශ්‍රිතය සපයන සේවාවට පටිගත කරන හෝ විකාශ කරන අතරතුර ඔබේ තිරයේ දිස් වන හෝ ඔබේ උපාංගයෙන් වාදනය කරන සියලු තොරතුරු වෙත ප්‍රවේශය ලැබෙනු ඇත. මෙහි මුරපද, ගෙවීම් විස්තර, ඡායාරූප, පණිවිඩ සහ ඔබ වාදනය කරන ඕඩියෝ යනාදි තොරතුරු ඇතුළත් වේ."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"පටිගත කිරීම හෝ විකාශය කිරීම ආරම්භ කරන්නද?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> සමග පටිගත කිරීම හෝ විකාශය කිරීම ආරම්භ කරන්නද?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> හට බෙදා ගැනීමට හෝ පටිගත කිරීමට ඉඩ දෙන්න ද?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"සම්පූර්ණ තිරය"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"තනි යෙදුමක්"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"ඔබ බෙදා ගන්නා විට, පටිගත කරන විට, හෝ විකාශනය කරන විට, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> හට ඔබේ තිරයේ පෙනෙන හෝ ඔබේ උපාංගයේ වාදනය වන ඕනෑම දෙයකට ප්‍රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්‍රවේශම් වන්න."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ඔබ යෙදුමක් බෙදා ගන්නා විට, පටිගත කරන විට හෝ විකාශය කරන විට, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> හට එම යෙදුමේ පෙන්වන හෝ වාදනය කරන ඕනෑම දෙයකට ප්‍රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්‍රවේශම් වන්න."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ඉදිරියට යන්න"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"යෙදුමක් බෙදා ගන්න හෝ පටිගත කරන්න"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"සියල්ල හිස් කරන්න"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"කළමනාකරණය කරන්න"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ඉතිහාසය"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index dcebddb..c1a83db 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Njoftim i vazhdueshëm për një seancë regjistrimi të ekranit"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Të niset regjistrimi?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Gjatë regjistrimit, sistemi Android mund të regjistrojë çdo informacion delikat që është i dukshëm në ekranin tënd ose që luhet në pajisje. Kjo përfshin fjalëkalimet, informacionin e pagesave, fotografitë, mesazhet dhe audion."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Regjistro të gjithë ekranin"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Regjistro vetëm një aplikacion"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Gjatë regjistrimit, Android ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në pajisjen tënde. Prandaj ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacione të tjera të ndjeshme."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Gjatë regjistrimit të një aplikacioni, Android ka qasje te çdo gjë e dukshme ose që po luhet në atë aplikacion. Prandaj, ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacione të tjera të ndjeshme."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Nis regjistrimin"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Regjistro audio"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audioja e pajisjes"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Tingulli nga pajisja, si muzika, telefonatat dhe tonet e ziles"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Shërbimi që e ofron këtë funksion do të ketë qasje te të gjitha informacionet që janë të dukshme në ekran ose që luhen nga pajisja jote gjatë regjistrimit ose transmetimit. Kjo përfshin informacione, si p.sh.: fjalëkalimet, detajet e pagesave, fotografitë, mesazhet dhe audion që luan ti."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Do të fillosh regjistrimin ose transmetimin?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Fillo regjistrimin ose transmetimin me <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Të lejohet <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> të shpërndajë ose regjistrojë?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Ekran i plotë"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Vetëm një aplikacion"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Gjatë shpërndarjes, regjistrimit ose transmetimit, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në pajisjen tënde. Prandaj ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacione të tjera të ndjeshme."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Gjatë shpërndarjes, regjistrimit ose transmetimit të një aplikacioni, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në atë aplikacion. Prandaj, ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacione të tjera të ndjeshme."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Vazhdo"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Shpërndaj ose regjistro një aplikacion"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Pastroji të gjitha"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Menaxho"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historiku"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 77f0d30..1778b01 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Avisering om att skärminspelning pågår"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Vill du starta inspelningen?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"När du spelar in kan Android-systemet registrera alla känsliga uppgifter som visas på skärmen eller spelas upp på enheten. Detta omfattar lösenord, betalningsuppgifter, foton, meddelanden och ljud."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Spela in hela skärmen"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Spela in en enda app"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"När du spelar in har Android åtkomst till allt som visas på skärmen eller spelas upp på enheten. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"När du spelar in en app har Android åtkomst till allt som visas eller spelas upp i appen. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Börja spela in"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Spela in ljud"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Ljud på enheten"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Ljud från enheten, till exempel musik, samtal och ringsignaler"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Den tjänst som tillhandahåller funktionen får åtkomst till all information som visas på skärmen eller spelas upp från enheten när du spelar in eller castar. Detta omfattar uppgifter som lösenord, betalningsinformation, foton, meddelanden och ljud som du spelar upp."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vill du börja spela in eller casta?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Vill du börja spela in eller casta med <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vill du tillåta att <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> delar eller spelar in?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Hela skärmen"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"En enda app"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"När du delar, spelar in eller castar har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> åtkomst till allt som visas på skärmen eller spelas upp på enheten. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"När du delar, spelar in eller castar en app har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> åtkomst till allt som visas eller spelas upp i appen. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Fortsätt"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Dela eller spela in en app"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Rensa alla"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Hantera"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historik"</string>
diff --git a/packages/SystemUI/res/values-sw600dp-land/styles.xml b/packages/SystemUI/res/values-sw600dp-land/styles.xml
new file mode 100644
index 0000000..8148d3d
--- /dev/null
+++ b/packages/SystemUI/res/values-sw600dp-land/styles.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <style name="AuthCredentialPatternContainerStyle">
+        <item name="android:gravity">center</item>
+        <item name="android:maxHeight">420dp</item>
+        <item name="android:maxWidth">420dp</item>
+        <item name="android:minHeight">200dp</item>
+        <item name="android:minWidth">200dp</item>
+        <item name="android:paddingHorizontal">120dp</item>
+        <item name="android:paddingVertical">40dp</item>
+    </style>
+
+    <style name="TextAppearance.AuthNonBioCredential.Title">
+        <item name="android:fontFamily">google-sans</item>
+        <item name="android:layout_marginTop">16dp</item>
+        <item name="android:textSize">36sp</item>
+        <item name="android:focusable">true</item>
+    </style>
+
+    <style name="TextAppearance.AuthNonBioCredential.Subtitle">
+        <item name="android:fontFamily">google-sans</item>
+        <item name="android:layout_marginTop">16dp</item>
+        <item name="android:textSize">18sp</item>
+    </style>
+
+    <style name="TextAppearance.AuthNonBioCredential.Description">
+        <item name="android:fontFamily">google-sans</item>
+        <item name="android:layout_marginTop">16dp</item>
+        <item name="android:textSize">18sp</item>
+    </style>
+</resources>
diff --git a/packages/SystemUI/res/values-sw600dp-port/styles.xml b/packages/SystemUI/res/values-sw600dp-port/styles.xml
new file mode 100644
index 0000000..771de08
--- /dev/null
+++ b/packages/SystemUI/res/values-sw600dp-port/styles.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <style name="AuthCredentialHeaderStyle">
+        <item name="android:paddingStart">120dp</item>
+        <item name="android:paddingEnd">120dp</item>
+        <item name="android:paddingTop">80dp</item>
+        <item name="android:paddingBottom">10dp</item>
+        <item name="android:layout_gravity">top</item>
+    </style>
+
+    <style name="AuthCredentialPatternContainerStyle">
+        <item name="android:gravity">center</item>
+        <item name="android:maxHeight">420dp</item>
+        <item name="android:maxWidth">420dp</item>
+        <item name="android:minHeight">200dp</item>
+        <item name="android:minWidth">200dp</item>
+        <item name="android:paddingHorizontal">180dp</item>
+        <item name="android:paddingVertical">80dp</item>
+    </style>
+
+    <style name="TextAppearance.AuthNonBioCredential.Title">
+        <item name="android:fontFamily">google-sans</item>
+        <item name="android:layout_marginTop">24dp</item>
+        <item name="android:textSize">36sp</item>
+        <item name="android:focusable">true</item>
+    </style>
+
+</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-land/styles.xml b/packages/SystemUI/res/values-sw720dp-land/styles.xml
new file mode 100644
index 0000000..f9ed67d
--- /dev/null
+++ b/packages/SystemUI/res/values-sw720dp-land/styles.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <style name="AuthCredentialPatternContainerStyle">
+        <item name="android:gravity">center</item>
+        <item name="android:maxHeight">420dp</item>
+        <item name="android:maxWidth">420dp</item>
+        <item name="android:minHeight">200dp</item>
+        <item name="android:minWidth">200dp</item>
+        <item name="android:paddingHorizontal">120dp</item>
+        <item name="android:paddingVertical">40dp</item>
+    </style>
+
+    <style name="TextAppearance.AuthNonBioCredential.Title">
+        <item name="android:fontFamily">google-sans</item>
+        <item name="android:layout_marginTop">16dp</item>
+        <item name="android:textSize">36sp</item>
+        <item name="android:focusable">true</item>
+    </style>
+
+    <style name="TextAppearance.AuthNonBioCredential.Subtitle">
+        <item name="android:fontFamily">google-sans</item>
+        <item name="android:layout_marginTop">16dp</item>
+        <item name="android:textSize">18sp</item>
+    </style>
+
+    <style name="TextAppearance.AuthNonBioCredential.Description">
+        <item name="android:fontFamily">google-sans</item>
+        <item name="android:layout_marginTop">16dp</item>
+        <item name="android:textSize">18sp</item>
+    </style>
+
+</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-port/styles.xml b/packages/SystemUI/res/values-sw720dp-port/styles.xml
new file mode 100644
index 0000000..78d299c
--- /dev/null
+++ b/packages/SystemUI/res/values-sw720dp-port/styles.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <style name="AuthCredentialHeaderStyle">
+        <item name="android:paddingStart">120dp</item>
+        <item name="android:paddingEnd">120dp</item>
+        <item name="android:paddingTop">80dp</item>
+        <item name="android:paddingBottom">10dp</item>
+        <item name="android:layout_gravity">top</item>
+    </style>
+
+    <style name="AuthCredentialPatternContainerStyle">
+        <item name="android:gravity">center</item>
+        <item name="android:maxHeight">420dp</item>
+        <item name="android:maxWidth">420dp</item>
+        <item name="android:minHeight">200dp</item>
+        <item name="android:minWidth">200dp</item>
+        <item name="android:paddingHorizontal">240dp</item>
+        <item name="android:paddingVertical">120dp</item>
+    </style>
+
+    <style name="TextAppearance.AuthNonBioCredential.Title">
+        <item name="android:fontFamily">google-sans</item>
+        <item name="android:layout_marginTop">24dp</item>
+        <item name="android:textSize">36sp</item>
+        <item name="android:focusable">true</item>
+    </style>
+
+</resources>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index c823f04..dbb90b3 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"திரை ரெக்கார்டிங் அமர்விற்கான தொடர் அறிவிப்பு"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"ரெக்கார்டிங்கைத் தொடங்கவா?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"ரெக்கார்டு செய்யும்போது, உங்கள் திரையில் தோன்றக்கூடிய அல்லது சாதனத்தில் பிளே ஆகக்கூடிய பாதுகாக்கப்பட வேண்டிய தகவலை Android சிஸ்டம் படமெடுக்க முடியும். கடவுச்சொற்கள், பேமெண்ட் தகவல், படங்கள், மெசேஜ்கள், ஆடியோ ஆகியவை இதில் அடங்கும்."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"முழு திரையை ரெக்கார்டு செய்தல்"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ஓர் ஆப்ஸை ரெக்கார்டு செய்தல்"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"நீங்கள் ரெக்கார்டு செய்யும்போது அந்தச் சாதனத்தில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தையும் Android அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், பிற பாதுகாக்கப்பட வேண்டிய தகவல்கள் ஆகியவை குறித்து கவனத்துடன் இருங்கள்."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ஓர் ஆப்ஸை நீங்கள் ரெக்கார்டு செய்யும்போது அந்த ஆப்ஸில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தையும் Android அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், பிற பாதுகாக்கப்பட வேண்டிய தகவல்கள் ஆகியவை குறித்து கவனத்துடன் இருங்கள்."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"ரெக்கார்டிங்கைத் தொடங்கு"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"ஆடியோவை ரெக்கார்டு செய்"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"சாதன ஆடியோ"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"இசை, அழைப்புகள், ரிங்டோன்கள் போன்ற உங்கள் சாதனத்திலிருந்து வரும் ஒலி"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"இந்தச் செயல்பாட்டை வழங்கும் சேவையானது உங்கள் திரையில் தெரியும் தகவல்கள், ரெக்கார்டு செய்யும்போதோ அனுப்பும்போதோ உங்கள் சாதனத்திலிருந்து பிளே ஆகும் அனைத்துத் தகவல்கள் ஆகியவற்றுக்கான அணுகலைக் கொண்டிருக்கும். கடவுச்சொற்கள், பேமெண்ட் தொடர்பான தகவல்கள், படங்கள், மெசேஜ்கள், நீங்கள் பிளே செய்யும் ஆடியோ போன்ற அனைத்துத் தகவல்களும் இதில் அடங்கும்."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ரெக்கார்டிங் செய்யவோ அனுப்புவோ தொடங்கவா?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> மூலம் ரெக்கார்டிங் செய்யவோ அனுப்புவதற்கோ தொடங்கிவீட்டீர்களா?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"பகிர அல்லது ரெக்கார்டு செய்ய <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ஆப்ஸை அனுமதிக்கலாமா?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"திரை முழுவதும்"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ஓர் ஆப்ஸ்"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"நீங்கள் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ உங்கள் சாதனத்தில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தையும் <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ஆப்ஸால் அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், பிற பாதுகாக்கப்பட வேண்டிய தகவல்கள் ஆகியவை குறித்து கவனத்துடன் இருங்கள்."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ஓர் ஆப்ஸை நீங்கள் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ அந்த ஆப்ஸில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தையும் <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ஆப்ஸால் அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், பிற பாதுகாக்கப்பட வேண்டிய தகவல்கள் ஆகியவை குறித்து கவனத்துடன் இருங்கள்."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"தொடர்க"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ஆப்ஸைப் பகிர்தல் அல்லது ரெக்கார்டு செய்தல்"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"எல்லாவற்றையும் அழி"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"நிர்வகி"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"இதுவரை வந்த அறிவிப்புகள்"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index e911bb2..a238f3f 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Ekran kaydı oturumu için devam eden bildirim"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Kayıt başlatılsın mı?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Kayıt sırasında Android Sistemi, ekranınızda görünen veya cihazınızda oynatılan hassas bilgileri yakalayabilir. Buna şifreler, ödeme bilgileri, fotoğraflar, mesajlar ve sesler dahildir."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Tüm ekranı kaydedin"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Tek bir uygulamayı kaydedin"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Kayıt özelliğini kullandığınızda Android, ekranınızda görünen veya cihazınızda oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Bir uygulamayı kaydetme özelliğini kullandığınızda Android, söz konusu uygulamada gösterilen veya oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Kaydı başlat"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Ses kaydet"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Cihaz sesi"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Müzik, aramalar, zil sesleri gibi cihazınızdan sesler"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Bu işlevi sağlayan hizmet, ekranınızda görünen veya kayıt ya da yayın sırasında cihazınızdan oynatılan tüm bilgilere erişecektir. Bu bilgiler arasında şifreler, ödeme detayları, fotoğraflar, mesajlar ve çaldığınız sesler gibi bilgiler yer alır."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Kayıt veya yayınlama başlatılsın mı?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ile kayıt veya yayınlama başlatılsın mı?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> uygulamasının paylaşmasına veya kaydetmesine izin verilsin mi?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Tüm ekran"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Tek bir uygulama"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Paylaşım, kayıt ve yayınlama özelliklerini kullandığınızda <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>, ekranınızda görünen veya cihazınızda oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Bir uygulamayı paylaşma, kaydetme ve yayınlama özelliklerini kullandığınızda <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>, söz konusu uygulamada gösterilen veya oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Devam"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Uygulamayı paylaşın veya kaydedin"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tümünü temizle"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Yönet"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Geçmiş"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 011c8bc..d3f392a 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Сповіщення про сеанс запису екрана"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"Почати запис?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"Під час запису система Android може фіксувати будь-яку конфіденційну інформацію, яка з\'являється на екрані або відтворюється на пристрої, зокрема паролі, платіжну інформацію, фотографії, повідомлення та звуки."</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Записувати весь екран"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Записувати окремий додаток"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Коли ви записуєте вміст екрана, ОС Android отримує доступ до всього, що відображається на ньому або відтворюється на пристрої. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Коли ви записуєте додаток, ОС Android отримує доступ до всього, що відображається або відтворюється в цьому додатку. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"Почати запис"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Записувати звук"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Звук із пристрою"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Звук із пристрою, зокрема музика, виклики та сигнали дзвінка"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Сервіс, що надає цю функцію, матиме доступ до всієї інформації, яка з\'являється на екрані або відтворюється на пристрої під час запису чи трансляції, зокрема до паролів, інформації про платежі, фотографій, повідомлень і аудіофайлів."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Почати запис або трансляцію?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Почати запис або трансляцію за допомогою додатка <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Дозволити додатку <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> показувати або записувати?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Увесь екран"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Окремий додаток"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Коли ви показуєте, записуєте або транслюєте екран, додаток <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> отримує доступ до всього, що відображається на екрані чи відтворюється на пристрої. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Коли ви показуєте, записуєте або транслюєте додаток, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> отримує доступ до всього, що відображається або відтворюється в цьому додатку. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Продовжити"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Показувати або записувати додаток"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Очистити все"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Керувати"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Історія"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 7d0df6c..1e71b14 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"اسکرین ریکارڈ سیشن کیلئے جاری اطلاع"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"ریکارڈنگ شروع کریں؟"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"‏ریکارڈ کرنے کے دوران، Android سسٹم آپ کی اسکرین پر نظر آنے والی یا آپ کے آلہ پر چلنے والی کسی بھی حساس معلومات کو کیپچر کر سکتا ہے۔ اس میں پاس ورڈز، ادائیگی کی معلومات، تصاویر، پیغامات اور آڈیو شامل ہیں۔"</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"پوری اسکرین کو ریکارڈ کریں"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"واحد ایپ کو ریکارڈ کریں"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"‏جب آپ ریکارڈنگ کر رہے ہوتے ہیں تو Android کو آپ کی اسکرین پر دکھائی دینے والی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"‏جب آپ کسی ایپ کو ریکارڈ کر رہے ہوتے ہیں تو Android کو اس ایپ پر دکھائی گئی یا چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"ریکارڈنگ شروع کریں"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"آڈیو ریکارڈ کریں"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"آلہ کا آڈیو"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"آپ کے آلے سے آواز، جیسے موسیقی، کالز اور رِنگ ٹونز"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"یہ فنکشن فراہم کرنے والی سروس کو اس تمام معلومات تک رسائی حاصل ہوگی جو آپ کی اسکرین پر نظر آتی ہے یا ریکارڈنگ یا کاسٹنگ کے دوران آپ کے آلے سے چلائی جاتی ہے۔ اس میں پاس ورڈز، ادائیگی کی تفصیلات، تصاویر، پیغامات اور وہ آڈیو جو آپ چلاتے ہیں جیسی معلومات شامل ہے۔"</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ریکارڈنگ یا کاسٹنگ شروع کریں؟"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> کے ذریعے ریکارڈنگ یا کاسٹنگ شروع کریں؟"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> کو اشتراک یا ریکارڈ کرنے کی اجازت دیں؟"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"پوری اسکرین"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"واحد ایپ"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"جب آپ اشتراک، ریکارڈنگ یا کاسٹ کر رہے ہوتے ہیں تو <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> کو آپ کی اسکرین پر دکھائی دینے والی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"جب آپ اشتراک، ریکارڈنگ یا کاسٹ کر رہے ہوتے ہیں تو <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> کو آپ کی اسکرین پر دکھائی گئی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"جاری رکھیں"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ایپ کا اشتراک یا ریکارڈ کریں"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"سبھی کو صاف کریں"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"نظم کریں"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"سرگزشت"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index d881fa2..87821bf 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -94,16 +94,11 @@
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"持续显示屏幕录制会话通知"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"要开始录制吗?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"在录制内容时,Android 系统可以捕捉到您屏幕上显示或设备中播放的敏感信息,其中包括密码、付款信息、照片、消息和音频。"</string>
-    <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
-    <skip />
-    <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
-    <skip />
-    <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
-    <skip />
-    <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
-    <skip />
+    <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"录制整个屏幕"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"录制单个应用"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"在您进行录制时,Android 可以访问您的屏幕显示或设备播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"在您录制某个应用时,Android 可以访问此应用显示或播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
+    <string name="screenrecord_start_recording" msgid="348286842544768740">"开始录制"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"录制音频"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"设备音频"</string>
     <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"设备发出的声音,例如音乐、通话和铃声"</string>
@@ -371,20 +366,13 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"在录制或投放内容时,提供此功能的服务将可获取您屏幕上显示或设备中播放的所有信息,其中包括密码、付款明细、照片、消息以及您播放的音频等信息。"</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"要开始录制或投放内容吗?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"要开始使用<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>录制或投放内容吗?"</string>
-    <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
-    <skip />
-    <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
-    <skip />
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"允许 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 分享或录制吗?"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"整个屏幕"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"单个应用"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"在您进行分享、录制或投射时,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以访问您的屏幕显示或设备播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"在您进行分享、录制或投射时,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以访问通过此应用显示或播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
+    <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"继续"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"分享或录制应用"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"历史记录"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index d934786..c703619 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -96,8 +96,8 @@
     <string name="screenrecord_description" msgid="1123231719680353736">"錄影時,Android 系統可擷取螢幕上顯示或裝置播放的任何敏感資料,包括密碼、付款資料、相片、訊息和音訊。"</string>
     <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"錄製整個螢幕畫面"</string>
     <string name="screenrecord_option_single_app" msgid="5954863081500035825">"錄製單一應用程式"</string>
-    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"進行錄製時,Android 可以存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
-    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"錄製應用程式時,Android 可以存取在該應用程式中顯示或播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"進行錄製時,Android 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"錄製應用程式時,Android 可存取在該應用程式中顯示或播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
     <string name="screenrecord_start_recording" msgid="348286842544768740">"開始錄製"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"錄音"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"裝置音訊"</string>
@@ -366,11 +366,11 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"在錄影或投放時,此功能的服務供應商可以存取螢幕顯示或裝置播放的任何資料,當中包括密碼、付款詳情、相片、訊息和播放的語音等。"</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"要開始錄影或投放嗎?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"要使用「<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>」開始錄影或投放嗎?"</string>
-    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"允許 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 分享或錄製?"</string>
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"允許 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 分享或錄製嗎?"</string>
     <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"整個螢幕畫面"</string>
     <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"單一應用程式"</string>
-    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"進行分享、錄製或投放時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
-    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"進行分享、錄製或投放應用程式時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以存取在該應用程式中顯示或播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"進行分享、錄製或投放時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"進行分享、錄製或投放時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"繼續"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"分享或錄製應用程式"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index bbe4b6c..f5fc7ee5 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -672,7 +672,7 @@
     <string name="data_connection_no_internet" msgid="691058178914184544">"沒有網際網路連線"</string>
     <string name="accessibility_quick_settings_open_settings" msgid="536838345505030893">"開啟「<xliff:g id="ID_1">%s</xliff:g>」設定。"</string>
     <string name="accessibility_quick_settings_edit" msgid="1523745183383815910">"編輯設定順序。"</string>
-    <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"電源按鈕選單"</string>
+    <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"電源鍵選單"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"第 <xliff:g id="ID_1">%1$d</xliff:g> 頁,共 <xliff:g id="ID_2">%2$d</xliff:g> 頁"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"鎖定畫面"</string>
     <string name="thermal_shutdown_title" msgid="2702966892682930264">"手機先前過熱,因此關閉電源"</string>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 9a71995..df0659d 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -191,5 +191,18 @@
     <declare-styleable name="DelayableMarqueeTextView">
         <attr name="marqueeDelay" format="integer" />
     </declare-styleable>
+
+    <declare-styleable name="AuthCredentialView">
+        <attr name="lockPatternStyle" format="reference" />
+        <attr name="lockPinPasswordStyle" format="reference" />
+        <attr name="containerStyle" format="reference" />
+        <attr name="headerStyle" format="reference" />
+        <attr name="headerIconStyle" format="reference" />
+        <attr name="titleTextAppearance" format="reference" />
+        <attr name="subTitleTextAppearance" format="reference" />
+        <attr name="descriptionTextAppearance" format="reference" />
+        <attr name="passwordTextAppearance" format="reference" />
+        <attr name="errorTextAppearance" format="reference"/>
+    </declare-styleable>
 </resources>
 
diff --git a/packages/SystemUI/res/values/bools.xml b/packages/SystemUI/res/values/bools.xml
index c67ac8d..8221d78 100644
--- a/packages/SystemUI/res/values/bools.xml
+++ b/packages/SystemUI/res/values/bools.xml
@@ -18,6 +18,13 @@
 <resources>
     <!-- Whether to show the user switcher in quick settings when only a single user is present. -->
     <bool name="qs_show_user_switcher_for_single_user">false</bool>
+
     <!-- Whether to show a custom biometric prompt size-->
     <bool name="use_custom_bp_size">false</bool>
+
+    <!-- Whether to enable clipping on Quick Settings -->
+    <bool name="qs_enable_clipping">true</bool>
+
+    <!-- Whether to enable transparent background for notification scrims -->
+    <bool name="notification_scrim_transparent">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 2eebdc6..93926ef9 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -519,7 +519,7 @@
     <dimen name="qs_tile_margin_horizontal">8dp</dimen>
     <dimen name="qs_tile_margin_vertical">@dimen/qs_tile_margin_horizontal</dimen>
     <dimen name="qs_tile_margin_top_bottom">4dp</dimen>
-    <dimen name="qs_brightness_margin_top">8dp</dimen>
+    <dimen name="qs_brightness_margin_top">12dp</dimen>
     <dimen name="qs_brightness_margin_bottom">16dp</dimen>
     <dimen name="qqs_layout_margin_top">16dp</dimen>
     <dimen name="qqs_layout_padding_bottom">24dp</dimen>
@@ -572,6 +572,7 @@
     <dimen name="qs_header_row_min_height">48dp</dimen>
 
     <dimen name="qs_header_non_clickable_element_height">24dp</dimen>
+    <dimen name="new_qs_header_non_clickable_element_height">20dp</dimen>
 
     <dimen name="qs_footer_padding">20dp</dimen>
     <dimen name="qs_security_footer_height">88dp</dimen>
@@ -949,6 +950,9 @@
     <dimen name="biometric_dialog_width">240dp</dimen>
     <dimen name="biometric_dialog_height">240dp</dimen>
 
+    <!-- Biometric Auth Credential values -->
+    <dimen name="biometric_auth_icon_size">48dp</dimen>
+
     <!-- Starting text size in sp of batteryLevel for wireless charging animation -->
     <item name="wireless_charging_anim_battery_level_text_size_start" format="float" type="dimen">
         0
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index 3164ed1..e30d441 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -28,4 +28,11 @@
 
     <!-- The time it takes for the over scroll release animation to complete, in milli seconds.  -->
     <integer name="lockscreen_shade_over_scroll_release_duration">0</integer>
+
+    <!-- Values for transition of QS Headers -->
+    <integer name="fade_out_complete_frame">14</integer>
+    <integer name="fade_in_start_frame">58</integer>
+    <!-- Percentage of displacement for items in QQS to guarantee matching with bottom of clock at
+         fade_out_complete_frame -->
+    <dimen name="percent_displacement_at_fade_out" format="float">0.1066</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9b9111f..212c77b5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -641,7 +641,7 @@
     <!-- QuickSettings: Label for the toggle that controls whether display color correction is enabled. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_color_correction_label">Color correction</string>
     <!-- QuickSettings: Control panel: Label for button that navigates to user settings. [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_more_user_settings">User settings</string>
+    <string name="quick_settings_more_user_settings">Manage users</string>
     <!-- QuickSettings: Control panel: Label for button that dismisses control panel. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_done">Done</string>
     <!-- QuickSettings: Control panel: Label for button that dismisses user switcher control panel. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index ac3eb7e..e76887b 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -128,11 +128,10 @@
     <!-- This is hard coded to be sans-serif-condensed to match the icons -->
 
     <style name="TextAppearance.QS.Status">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:textSize">14sp</item>
         <item name="android:letterSpacing">0.01</item>
-        <item name="android:lineHeight">20sp</item>
     </style>
 
     <style name="TextAppearance.QS.SecurityFooter" parent="@style/TextAppearance.QS.Status">
@@ -143,12 +142,10 @@
     <style name="TextAppearance.QS.Status.Carriers" />
 
     <style name="TextAppearance.QS.Status.Carriers.NoCarrierText">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
         <item name="android:textColor">?android:attr/textColorSecondary</item>
     </style>
 
     <style name="TextAppearance.QS.Status.Build">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
         <item name="android:textColor">?android:attr/textColorSecondary</item>
     </style>
 
@@ -200,8 +197,9 @@
 
     <style name="TextAppearance.AuthNonBioCredential.Title">
         <item name="android:fontFamily">google-sans</item>
-        <item name="android:layout_marginTop">20dp</item>
-        <item name="android:textSize">36sp</item>
+        <item name="android:layout_marginTop">24dp</item>
+        <item name="android:textSize">36dp</item>
+        <item name="android:focusable">true</item>
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Subtitle">
@@ -213,12 +211,10 @@
     <style name="TextAppearance.AuthNonBioCredential.Description">
         <item name="android:fontFamily">google-sans</item>
         <item name="android:layout_marginTop">20dp</item>
-        <item name="android:textSize">16sp</item>
+        <item name="android:textSize">18sp</item>
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Error">
-        <item name="android:paddingTop">6dp</item>
-        <item name="android:paddingBottom">18dp</item>
         <item name="android:paddingHorizontal">24dp</item>
         <item name="android:textSize">14sp</item>
         <item name="android:textColor">?android:attr/colorError</item>
@@ -227,20 +223,43 @@
 
     <style name="TextAppearance.AuthCredential.PasswordEntry" parent="@android:style/TextAppearance.DeviceDefault">
         <item name="android:gravity">center</item>
+        <item name="android:paddingTop">28dp</item>
         <item name="android:singleLine">true</item>
         <item name="android:textColor">?android:attr/colorForeground</item>
         <item name="android:textSize">24sp</item>
+        <item name="android:background">@drawable/edit_text_filled</item>
     </style>
 
     <style name="AuthCredentialHeaderStyle">
         <item name="android:paddingStart">48dp</item>
-        <item name="android:paddingEnd">24dp</item>
-        <item name="android:paddingTop">28dp</item>
-        <item name="android:paddingBottom">20dp</item>
-        <item name="android:orientation">vertical</item>
+        <item name="android:paddingEnd">48dp</item>
+        <item name="android:paddingTop">48dp</item>
+        <item name="android:paddingBottom">10dp</item>
         <item name="android:layout_gravity">top</item>
     </style>
 
+    <style name="AuthCredentialIconStyle">
+        <item name="android:layout_width">@dimen/biometric_auth_icon_size</item>
+        <item name="android:layout_height">@dimen/biometric_auth_icon_size</item>
+    </style>
+
+    <style name="AuthCredentialPatternContainerStyle">
+        <item name="android:gravity">center</item>
+        <item name="android:maxHeight">420dp</item>
+        <item name="android:maxWidth">420dp</item>
+        <item name="android:minHeight">200dp</item>
+        <item name="android:minWidth">200dp</item>
+        <item name="android:padding">20dp</item>
+    </style>
+
+    <style name="AuthCredentialPinPasswordContainerStyle">
+        <item name="android:gravity">center</item>
+        <item name="android:maxHeight">48dp</item>
+        <item name="android:maxWidth">600dp</item>
+        <item name="android:minHeight">48dp</item>
+        <item name="android:minWidth">200dp</item>
+    </style>
+
     <style name="DeviceManagementDialogTitle">
         <item name="android:gravity">center</item>
         <item name="android:textAppearance">@style/TextAppearance.DeviceManagementDialog.Title</item>
@@ -278,7 +297,9 @@
         <item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_dark</item>
         <item name="wallpaperTextColorAccent">@color/material_dynamic_primary90</item>
         <item name="android:colorError">@*android:color/error_color_material_dark</item>
-        <item name="*android:lockPatternStyle">@style/LockPatternStyle</item>
+        <item name="*android:lockPatternStyle">@style/LockPatternViewStyle</item>
+        <item name="lockPatternStyle">@style/LockPatternContainerStyle</item>
+        <item name="lockPinPasswordStyle">@style/LockPinPasswordContainerStyle</item>
         <item name="passwordStyle">@style/PasswordTheme</item>
         <item name="numPadKeyStyle">@style/NumPadKey</item>
         <item name="backgroundProtectedStyle">@style/BackgroundProtectedStyle</item>
@@ -304,27 +325,33 @@
         <item name="android:textColor">?attr/wallpaperTextColor</item>
     </style>
 
-    <style name="LockPatternContainerStyle">
-        <item name="android:maxHeight">400dp</item>
-        <item name="android:maxWidth">420dp</item>
-        <item name="android:minHeight">0dp</item>
-        <item name="android:minWidth">0dp</item>
-        <item name="android:paddingHorizontal">60dp</item>
-        <item name="android:paddingBottom">40dp</item>
+    <style name="AuthCredentialStyle">
+        <item name="*android:regularColor">?android:attr/colorForeground</item>
+        <item name="*android:successColor">?android:attr/colorForeground</item>
+        <item name="*android:errorColor">?android:attr/colorError</item>
+        <item name="*android:dotColor">?android:attr/textColorSecondary</item>
+        <item name="headerStyle">@style/AuthCredentialHeaderStyle</item>
+        <item name="headerIconStyle">@style/AuthCredentialIconStyle</item>
+        <item name="titleTextAppearance">@style/TextAppearance.AuthNonBioCredential.Title</item>
+        <item name="subTitleTextAppearance">@style/TextAppearance.AuthNonBioCredential.Subtitle</item>
+        <item name="descriptionTextAppearance">@style/TextAppearance.AuthNonBioCredential.Description</item>
+        <item name="passwordTextAppearance">@style/TextAppearance.AuthCredential.PasswordEntry</item>
+        <item name="errorTextAppearance">@style/TextAppearance.AuthNonBioCredential.Error</item>
     </style>
 
-    <style name="LockPatternStyle">
+    <style name="LockPatternViewStyle" >
         <item name="*android:regularColor">?android:attr/colorAccent</item>
         <item name="*android:successColor">?android:attr/textColorPrimary</item>
         <item name="*android:errorColor">?android:attr/colorError</item>
         <item name="*android:dotColor">?android:attr/textColorSecondary</item>
     </style>
 
-    <style name="LockPatternStyleBiometricPrompt">
-        <item name="*android:regularColor">?android:attr/colorForeground</item>
-        <item name="*android:successColor">?android:attr/colorForeground</item>
-        <item name="*android:errorColor">?android:attr/colorError</item>
-        <item name="*android:dotColor">?android:attr/textColorSecondary</item>
+    <style name="LockPatternContainerStyle" parent="@style/AuthCredentialStyle">
+        <item name="containerStyle">@style/AuthCredentialPatternContainerStyle</item>
+    </style>
+
+    <style name="LockPinPasswordContainerStyle" parent="@style/AuthCredentialStyle">
+        <item name="containerStyle">@style/AuthCredentialPinPasswordContainerStyle</item>
     </style>
 
     <style name="Theme.SystemUI.QuickSettings" parent="@*android:style/Theme.DeviceDefault">
diff --git a/packages/SystemUI/res/xml/combined_qs_header_scene.xml b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
index f3866c0..de855e2 100644
--- a/packages/SystemUI/res/xml/combined_qs_header_scene.xml
+++ b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
@@ -27,67 +27,60 @@
             <KeyPosition
                 app:keyPositionType="deltaRelative"
                 app:percentX="0"
-                app:percentY="0"
-                app:framePosition="49"
+                app:percentY="@dimen/percent_displacement_at_fade_out"
+                app:framePosition="@integer/fade_out_complete_frame"
                 app:sizePercent="0"
                 app:curveFit="linear"
                 app:motionTarget="@id/date" />
             <KeyPosition
                 app:keyPositionType="deltaRelative"
                 app:percentX="1"
-                app:percentY="0.51"
+                app:percentY="0.5"
                 app:sizePercent="1"
-                app:framePosition="51"
+                app:framePosition="50"
                 app:curveFit="linear"
                 app:motionTarget="@id/date" />
             <KeyAttribute
                 app:motionTarget="@id/date"
-                app:framePosition="30"
+                app:framePosition="14"
                 android:alpha="0"
                 />
             <KeyAttribute
                 app:motionTarget="@id/date"
-                app:framePosition="70"
+                app:framePosition="@integer/fade_in_start_frame"
                 android:alpha="0"
                 />
             <KeyPosition
-                app:keyPositionType="pathRelative"
+                app:keyPositionType="deltaRelative"
                 app:percentX="0"
-                app:percentY="0"
-                app:framePosition="0"
-                app:curveFit="linear"
-                app:motionTarget="@id/statusIcons" />
-            <KeyPosition
-                app:keyPositionType="pathRelative"
-                app:percentX="0"
-                app:percentY="0"
-                app:framePosition="50"
+                app:percentY="@dimen/percent_displacement_at_fade_out"
+                app:framePosition="@integer/fade_out_complete_frame"
                 app:sizePercent="0"
                 app:curveFit="linear"
                 app:motionTarget="@id/statusIcons" />
             <KeyPosition
                 app:keyPositionType="deltaRelative"
                 app:percentX="1"
-                app:percentY="0.51"
-                app:framePosition="51"
+                app:percentY="0.5"
+                app:framePosition="50"
                 app:sizePercent="1"
                 app:curveFit="linear"
                 app:motionTarget="@id/statusIcons" />
             <KeyAttribute
                 app:motionTarget="@id/statusIcons"
-                app:framePosition="30"
+                app:framePosition="@integer/fade_out_complete_frame"
                 android:alpha="0"
                 />
             <KeyAttribute
                 app:motionTarget="@id/statusIcons"
-                app:framePosition="70"
+                app:framePosition="@integer/fade_in_start_frame"
                 android:alpha="0"
                 />
             <KeyPosition
                 app:keyPositionType="deltaRelative"
                 app:percentX="0"
-                app:percentY="0"
-                app:framePosition="50"
+                app:percentY="@dimen/percent_displacement_at_fade_out"
+                app:framePosition="@integer/fade_out_complete_frame"
                 app:percentWidth="1"
                 app:percentHeight="1"
                 app:curveFit="linear"
@@ -95,27 +88,27 @@
             <KeyPosition
                 app:keyPositionType="deltaRelative"
                 app:percentX="1"
-                app:percentY="0.51"
-                app:framePosition="51"
+                app:percentY="0.5"
+                app:framePosition="50"
                 app:percentWidth="1"
                 app:percentHeight="1"
                 app:curveFit="linear"
                 app:motionTarget="@id/batteryRemainingIcon" />
             <KeyAttribute
                 app:motionTarget="@id/batteryRemainingIcon"
-                app:framePosition="30"
+                app:framePosition="@integer/fade_out_complete_frame"
                 android:alpha="0"
                 />
             <KeyAttribute
                 app:motionTarget="@id/batteryRemainingIcon"
-                app:framePosition="70"
+                app:framePosition="@integer/fade_in_start_frame"
                 android:alpha="0"
                 />
             <KeyPosition
                 app:motionTarget="@id/carrier_group"
                 app:percentX="1"
-                app:percentY="0.51"
-                app:framePosition="51"
+                app:percentY="0.5"
+                app:framePosition="50"
                 app:percentWidth="1"
                 app:percentHeight="1"
                 app:curveFit="linear"
@@ -126,7 +119,7 @@
                 android:alpha="0" />
             <KeyAttribute
                 app:motionTarget="@id/carrier_group"
-                app:framePosition="70"
+                app:framePosition="@integer/fade_in_start_frame"
                 android:alpha="0" />
         </KeyFrameSet>
     </Transition>
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index a82684d03..88b4f43 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -43,7 +43,8 @@
         android:id="@+id/date">
         <Layout
             android:layout_width="0dp"
-            android:layout_height="@dimen/qs_header_non_clickable_element_height"
+            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+            android:layout_marginStart="8dp"
             app:layout_constrainedWidth="true"
             app:layout_constraintStart_toEndOf="@id/clock"
             app:layout_constraintEnd_toStartOf="@id/barrier"
@@ -57,8 +58,8 @@
         android:id="@+id/statusIcons">
         <Layout
             android:layout_width="0dp"
-            android:layout_height="@dimen/qs_header_non_clickable_element_height"
-            app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
+            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+            app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constraintStart_toEndOf="@id/date"
             app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
             app:layout_constraintTop_toTopOf="parent"
@@ -71,9 +72,9 @@
         android:id="@+id/batteryRemainingIcon">
         <Layout
             android:layout_width="wrap_content"
-            android:layout_height="@dimen/qs_header_non_clickable_element_height"
+            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constrainedWidth="true"
-            app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
+            app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constraintStart_toEndOf="@id/statusIcons"
             app:layout_constraintEnd_toEndOf="@id/end_guide"
             app:layout_constraintTop_toTopOf="parent"
diff --git a/packages/SystemUI/res/xml/qs_header_new.xml b/packages/SystemUI/res/xml/qs_header_new.xml
index f39e6bd..d8a4e77 100644
--- a/packages/SystemUI/res/xml/qs_header_new.xml
+++ b/packages/SystemUI/res/xml/qs_header_new.xml
@@ -40,13 +40,13 @@
             android:layout_height="@dimen/large_screen_shade_header_min_height"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@id/privacy_container"
-            app:layout_constraintBottom_toTopOf="@id/date"
+            app:layout_constraintBottom_toBottomOf="@id/carrier_group"
             app:layout_constraintEnd_toStartOf="@id/carrier_group"
             app:layout_constraintHorizontal_bias="0"
         />
         <Transform
-            android:scaleX="2.4"
-            android:scaleY="2.4"
+            android:scaleX="2.57"
+            android:scaleY="2.57"
             />
     </Constraint>
 
@@ -54,11 +54,11 @@
         android:id="@+id/date">
         <Layout
             android:layout_width="0dp"
-            android:layout_height="@dimen/qs_header_non_clickable_element_height"
+            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintEnd_toStartOf="@id/space"
             app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/clock"
+            app:layout_constraintTop_toBottomOf="@id/carrier_group"
             app:layout_constraintHorizontal_bias="0"
             app:layout_constraintHorizontal_chainStyle="spread_inside"
         />
@@ -87,7 +87,7 @@
         android:id="@+id/statusIcons">
         <Layout
             android:layout_width="0dp"
-            android:layout_height="@dimen/qs_header_non_clickable_element_height"
+            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constrainedWidth="true"
             app:layout_constraintStart_toEndOf="@id/space"
             app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
@@ -101,8 +101,8 @@
         android:id="@+id/batteryRemainingIcon">
         <Layout
             android:layout_width="wrap_content"
-            android:layout_height="@dimen/qs_header_non_clickable_element_height"
-            app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
+            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+            app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constraintStart_toEndOf="@id/statusIcons"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="@id/date"
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
index 2e391c7..49cc483 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
@@ -19,6 +19,7 @@
 import android.app.Activity
 import android.graphics.Color
 import android.view.View
+import android.view.Window
 import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
 import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.WindowInsetsControllerCompat
@@ -51,13 +52,14 @@
 
     /**
      * Compare the content of the [view] with the golden image identified by [goldenIdentifier] in
-     * the context of [emulationSpec].
+     * the context of [emulationSpec]. Window must be specified to capture views that render
+     * hardware buffers.
      */
-    fun screenshotTest(goldenIdentifier: String, view: View) {
+    fun screenshotTest(goldenIdentifier: String, view: View, window: Window? = null) {
         view.removeElevationRecursively()
 
         ScreenshotRuleAsserter.Builder(screenshotRule)
-            .setScreenshotProvider { view.toBitmap() }
+            .setScreenshotProvider { view.toBitmap(window) }
             .withMatcher(matcher)
             .build()
             .assertGoldenImage(goldenIdentifier)
@@ -94,6 +96,6 @@
             activity.currentFocus?.clearFocus()
         }
 
-        screenshotTest(goldenIdentifier, rootView)
+        screenshotTest(goldenIdentifier, rootView, activity.window)
     }
 }
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 18bd6b4..91fd6a6 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -59,7 +59,9 @@
     resource_dirs: [
         "res",
     ],
-    java_version: "1.8",
+    optimize: {
+        proguard_flags_files: ["proguard.flags"],
+    },
     min_sdk_version: "current",
     plugins: ["dagger2-compiler"],
 }
diff --git a/packages/SystemUI/shared/proguard.flags b/packages/SystemUI/shared/proguard.flags
new file mode 100644
index 0000000..5eda045
--- /dev/null
+++ b/packages/SystemUI/shared/proguard.flags
@@ -0,0 +1,4 @@
+# Retain signatures of TypeToken and its subclasses for gson usage in ClockRegistry
+-keepattributes Signature
+-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
+-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 860a5da..236aa66 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -20,25 +20,28 @@
 import android.annotation.FloatRange
 import android.annotation.IntRange
 import android.annotation.SuppressLint
-import android.app.compat.ChangeIdStateCache.invalidate
 import android.content.Context
 import android.graphics.Canvas
+import android.graphics.Rect
 import android.text.Layout
 import android.text.TextUtils
 import android.text.format.DateFormat
 import android.util.AttributeSet
+import android.util.MathUtils
 import android.widget.TextView
-import com.android.internal.R.attr.contentDescription
-import com.android.internal.R.attr.format
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.animation.GlyphCallback
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.TextAnimator
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
 import com.android.systemui.shared.R
 import java.io.PrintWriter
 import java.util.Calendar
 import java.util.Locale
 import java.util.TimeZone
+import kotlin.math.max
+import kotlin.math.min
 
 /**
  * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
@@ -51,14 +54,8 @@
     defStyleAttr: Int = 0,
     defStyleRes: Int = 0
 ) : TextView(context, attrs, defStyleAttr, defStyleRes) {
-
-    private var lastMeasureCall: CharSequence? = null
-    private var lastDraw: CharSequence? = null
-    private var lastTextUpdate: CharSequence? = null
-    private var lastOnTextChanged: CharSequence? = null
-    private var lastInvalidate: CharSequence? = null
-    private var lastTimeZoneChange: CharSequence? = null
-    private var lastAnimationCall: CharSequence? = null
+    var tag: String = "UnnamedClockView"
+    var logBuffer: LogBuffer? = null
 
     private val time = Calendar.getInstance()
 
@@ -135,6 +132,7 @@
 
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
+        logBuffer?.log(tag, DEBUG, "onAttachedToWindow")
         refreshFormat()
     }
 
@@ -150,27 +148,39 @@
         time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis()
         contentDescription = DateFormat.format(descFormat, time)
         val formattedText = DateFormat.format(format, time)
+        logBuffer?.log(tag, DEBUG,
+                { str1 = formattedText?.toString() },
+                { "refreshTime: new formattedText=$str1" }
+        )
         // Setting text actually triggers a layout pass (because the text view is set to
         // wrap_content width and TextView always relayouts for this). Avoid needless
         // relayout if the text didn't actually change.
         if (!TextUtils.equals(text, formattedText)) {
             text = formattedText
+            logBuffer?.log(tag, DEBUG,
+                    { str1 = formattedText?.toString() },
+                    { "refreshTime: done setting new time text to: $str1" }
+            )
             // Because the TextLayout may mutate under the hood as a result of the new text, we
             // notify the TextAnimator that it may have changed and request a measure/layout. A
             // crash will occur on the next invocation of setTextStyle if the layout is mutated
             // without being notified TextInterpolator being notified.
             if (layout != null) {
                 textAnimator?.updateLayout(layout)
+                logBuffer?.log(tag, DEBUG, "refreshTime: done updating textAnimator layout")
             }
             requestLayout()
-            lastTextUpdate = getTimestamp()
+            logBuffer?.log(tag, DEBUG, "refreshTime: after requestLayout")
         }
     }
 
     fun onTimeZoneChanged(timeZone: TimeZone?) {
         time.timeZone = timeZone
         refreshFormat()
-        lastTimeZoneChange = "${getTimestamp()} timeZone=${time.timeZone}"
+        logBuffer?.log(tag, DEBUG,
+                { str1 = timeZone?.toString() },
+                { "onTimeZoneChanged newTimeZone=$str1" }
+        )
     }
 
     @SuppressLint("DrawAllocation")
@@ -184,22 +194,24 @@
         } else {
             animator.updateLayout(layout)
         }
-        lastMeasureCall = getTimestamp()
+        logBuffer?.log(tag, DEBUG, "onMeasure")
     }
 
     override fun onDraw(canvas: Canvas) {
-        lastDraw = getTimestamp()
-        // intentionally doesn't call super.onDraw here or else the text will be rendered twice
-        textAnimator?.draw(canvas)
+        // Use textAnimator to render text if animation is enabled.
+        // Otherwise default to using standard draw functions.
+        if (isAnimationEnabled) {
+            // intentionally doesn't call super.onDraw here or else the text will be rendered twice
+            textAnimator?.draw(canvas)
+        } else {
+            super.onDraw(canvas)
+        }
+        logBuffer?.log(tag, DEBUG, "onDraw lastDraw")
     }
 
     override fun invalidate() {
         super.invalidate()
-        lastInvalidate = getTimestamp()
-    }
-
-    private fun getTimestamp(): CharSequence {
-        return "${DateFormat.format("HH:mm:ss", System.currentTimeMillis())} text=$text"
+        logBuffer?.log(tag, DEBUG, "invalidate")
     }
 
     override fun onTextChanged(
@@ -209,7 +221,10 @@
             lengthAfter: Int
     ) {
         super.onTextChanged(text, start, lengthBefore, lengthAfter)
-        lastOnTextChanged = "${getTimestamp()}"
+        logBuffer?.log(tag, DEBUG,
+                { str1 = text.toString() },
+                { "onTextChanged text=$str1" }
+        )
     }
 
     fun setLineSpacingScale(scale: Float) {
@@ -223,7 +238,7 @@
     }
 
     fun animateAppearOnLockscreen() {
-        lastAnimationCall = "${getTimestamp()} call=animateAppearOnLockscreen"
+        logBuffer?.log(tag, DEBUG, "animateAppearOnLockscreen")
         setTextStyle(
             weight = dozingWeight,
             textSize = -1f,
@@ -248,7 +263,7 @@
         if (isAnimationEnabled && textAnimator == null) {
             return
         }
-        lastAnimationCall = "${getTimestamp()} call=animateFoldAppear"
+        logBuffer?.log(tag, DEBUG, "animateFoldAppear")
         setTextStyle(
             weight = lockScreenWeightInternal,
             textSize = -1f,
@@ -275,7 +290,7 @@
             // Skip charge animation if dozing animation is already playing.
             return
         }
-        lastAnimationCall = "${getTimestamp()} call=animateCharge"
+        logBuffer?.log(tag, DEBUG, "animateCharge")
         val startAnimPhase2 = Runnable {
             setTextStyle(
                 weight = if (isDozing()) dozingWeight else lockScreenWeight,
@@ -299,7 +314,7 @@
     }
 
     fun animateDoze(isDozing: Boolean, animate: Boolean) {
-        lastAnimationCall = "${getTimestamp()} call=animateDoze"
+        logBuffer?.log(tag, DEBUG, "animateDoze")
         setTextStyle(
             weight = if (isDozing) dozingWeight else lockScreenWeight,
             textSize = -1f,
@@ -311,7 +326,24 @@
         )
     }
 
-    private val glyphFilter: GlyphCallback? = null // Add text animation tweak here.
+    // The offset of each glyph from where it should be.
+    private var glyphOffsets = mutableListOf(0.0f, 0.0f, 0.0f, 0.0f)
+
+    private var lastSeenAnimationProgress = 1.0f
+
+    // If the animation is being reversed, the target offset for each glyph for the "stop".
+    private var animationCancelStartPosition = mutableListOf(0.0f, 0.0f, 0.0f, 0.0f)
+    private var animationCancelStopPosition = 0.0f
+
+    // Whether the currently playing animation needed a stop (and thus, is shortened).
+    private var currentAnimationNeededStop = false
+
+    private val glyphFilter: GlyphCallback = { positionedGlyph, _ ->
+        val offset = positionedGlyph.lineNo * DIGITS_PER_LINE + positionedGlyph.glyphIndex
+        if (offset < glyphOffsets.size) {
+            positionedGlyph.x += glyphOffsets[offset]
+        }
+    }
 
     /**
      * Set text style with an optional animation.
@@ -345,6 +377,9 @@
                 onAnimationEnd = onAnimationEnd
             )
             textAnimator?.glyphFilter = glyphFilter
+            if (color != null && !isAnimationEnabled) {
+                setTextColor(color)
+            }
         } else {
             // when the text animator is set, update its start values
             onTextAnimatorInitialized = Runnable {
@@ -359,6 +394,9 @@
                     onAnimationEnd = onAnimationEnd
                 )
                 textAnimator?.glyphFilter = glyphFilter
+                if (color != null && !isAnimationEnabled) {
+                    setTextColor(color)
+                }
             }
         }
     }
@@ -394,9 +432,12 @@
             isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
             else -> DOUBLE_LINE_FORMAT_12_HOUR
         }
+        logBuffer?.log(tag, DEBUG,
+                { str1 = format?.toString() },
+                { "refreshFormat format=$str1" }
+        )
 
         descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12
-
         refreshTime()
     }
 
@@ -405,15 +446,8 @@
         pw.println("    measuredWidth=$measuredWidth")
         pw.println("    measuredHeight=$measuredHeight")
         pw.println("    singleLineInternal=$isSingleLineInternal")
-        pw.println("    lastTextUpdate=$lastTextUpdate")
-        pw.println("    lastOnTextChanged=$lastOnTextChanged")
-        pw.println("    lastInvalidate=$lastInvalidate")
-        pw.println("    lastMeasureCall=$lastMeasureCall")
-        pw.println("    lastDraw=$lastDraw")
-        pw.println("    lastTimeZoneChange=$lastTimeZoneChange")
         pw.println("    currText=$text")
         pw.println("    currTimeContextDesc=$contentDescription")
-        pw.println("    lastAnimationCall=$lastAnimationCall")
         pw.println("    dozingWeightInternal=$dozingWeightInternal")
         pw.println("    lockScreenWeightInternal=$lockScreenWeightInternal")
         pw.println("    dozingColor=$dozingColor")
@@ -421,6 +455,124 @@
         pw.println("    time=$time")
     }
 
+    fun moveForSplitShade(fromRect: Rect, toRect: Rect, fraction: Float) {
+        // Do we need to cancel an in-flight animation?
+        // Need to also check against 0.0f here; we can sometimes get two calls with fraction == 0,
+        // which trips up the check otherwise.
+        if (lastSeenAnimationProgress != 1.0f &&
+                lastSeenAnimationProgress != 0.0f &&
+                fraction == 0.0f) {
+            // New animation, but need to stop the old one. Figure out where each glyph currently
+            // is in relation to the box position. After that, use the leading digit's current
+            // position as the stop target.
+            currentAnimationNeededStop = true
+
+            // We assume that the current glyph offsets would be relative to the "from" position.
+            val moveAmount = toRect.left - fromRect.left
+
+            // Remap the current glyph offsets to be relative to the new "end" position, and figure
+            // out the start/end positions for the stop animation.
+            for (i in 0 until NUM_DIGITS) {
+                glyphOffsets[i] = -moveAmount + glyphOffsets[i]
+                animationCancelStartPosition[i] = glyphOffsets[i]
+            }
+
+            // Use the leading digit's offset as the stop position.
+            if (toRect.left > fromRect.left) {
+                // It _was_ moving left
+                animationCancelStopPosition = glyphOffsets[0]
+            } else {
+                // It was moving right
+                animationCancelStopPosition = glyphOffsets[1]
+            }
+        }
+
+        // Is there a cancellation in progress?
+        if (currentAnimationNeededStop && fraction < ANIMATION_CANCELLATION_TIME) {
+            val animationStopProgress = MathUtils.constrainedMap(
+                    0.0f, 1.0f, 0.0f, ANIMATION_CANCELLATION_TIME, fraction
+            )
+
+            // One of the digits has already stopped.
+            val animationStopStep = 1.0f / (NUM_DIGITS - 1)
+
+            for (i in 0 until NUM_DIGITS) {
+                val stopAmount = if (toRect.left > fromRect.left) {
+                    // It was moving left (before flipping)
+                    MOVE_LEFT_DELAYS[i] * animationStopStep
+                } else {
+                    // It was moving right (before flipping)
+                    MOVE_RIGHT_DELAYS[i] * animationStopStep
+                }
+
+                // Leading digit stops immediately.
+                if (stopAmount == 0.0f) {
+                    glyphOffsets[i] = animationCancelStopPosition
+                } else {
+                    val actualStopAmount = MathUtils.constrainedMap(
+                            0.0f, 1.0f, 0.0f, stopAmount, animationStopProgress
+                    )
+                    val easedProgress = MOVE_INTERPOLATOR.getInterpolation(actualStopAmount)
+                    val glyphMoveAmount =
+                            animationCancelStopPosition - animationCancelStartPosition[i]
+                    glyphOffsets[i] =
+                            animationCancelStartPosition[i] + glyphMoveAmount * easedProgress
+                }
+            }
+        } else {
+            // Normal part of the animation.
+            // Do we need to remap the animation progress to take account of the cancellation?
+            val actualFraction = if (currentAnimationNeededStop) {
+                MathUtils.constrainedMap(
+                        0.0f, 1.0f, ANIMATION_CANCELLATION_TIME, 1.0f, fraction
+                )
+            } else {
+                fraction
+            }
+
+            val digitFractions = (0 until NUM_DIGITS).map {
+                // The delay for each digit, in terms of fraction (i.e. the digit should not move
+                // during 0.0 - 0.1).
+                val initialDelay = if (toRect.left > fromRect.left) {
+                    MOVE_RIGHT_DELAYS[it] * MOVE_DIGIT_STEP
+                } else {
+                    MOVE_LEFT_DELAYS[it] * MOVE_DIGIT_STEP
+                }
+
+                val f = MathUtils.constrainedMap(
+                        0.0f, 1.0f,
+                        initialDelay, initialDelay + AVAILABLE_ANIMATION_TIME,
+                        actualFraction
+                )
+                MOVE_INTERPOLATOR.getInterpolation(max(min(f, 1.0f), 0.0f))
+            }
+
+            // Was there an animation halt?
+            val moveAmount = if (currentAnimationNeededStop) {
+                // Only need to animate over the remaining space if the animation was aborted.
+                -animationCancelStopPosition
+            } else {
+                toRect.left.toFloat() - fromRect.left.toFloat()
+            }
+
+            for (i in 0 until NUM_DIGITS) {
+                glyphOffsets[i] = -moveAmount + (moveAmount * digitFractions[i])
+            }
+        }
+
+        invalidate()
+
+        if (fraction == 1.0f) {
+            // Reset
+            currentAnimationNeededStop = false
+        }
+
+        lastSeenAnimationProgress = fraction
+
+        // Ensure that the actual clock container is always in the "end" position.
+        this.setLeftTopRightBottom(toRect.left, toRect.top, toRect.right, toRect.bottom)
+    }
+
     // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
     // This is an optimization to ensure we only recompute the patterns when the inputs change.
     private object Patterns {
@@ -444,6 +596,7 @@
             if (!clockView12Skel.contains("a")) {
                 sClockView12 = clockView12.replace("a".toRegex(), "").trim { it <= ' ' }
             }
+
             sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel)
             sCacheKey = key
         }
@@ -458,5 +611,36 @@
         private const val APPEAR_ANIM_DURATION: Long = 350
         private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500
         private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
+
+        // Constants for the animation
+        private val MOVE_INTERPOLATOR = Interpolators.STANDARD
+
+        // Calculate the positions of all of the digits...
+        // Offset each digit by, say, 0.1
+        // This means that each digit needs to move over a slice of "fractions", i.e. digit 0 should
+        // move from 0.0 - 0.7, digit 1 from 0.1 - 0.8, digit 2 from 0.2 - 0.9, and digit 3
+        // from 0.3 - 1.0.
+        private const val NUM_DIGITS = 4
+        private const val DIGITS_PER_LINE = 2
+
+        // How much of "fraction" to spend on canceling the animation, if needed
+        private const val ANIMATION_CANCELLATION_TIME = 0.4f
+
+        // Delays. Each digit's animation should have a slight delay, so we get a nice
+        // "stepping" effect. When moving right, the second digit of the hour should move first.
+        // When moving left, the first digit of the hour should move first. The lists encode
+        // the delay for each digit (hour[0], hour[1], minute[0], minute[1]), to be multiplied
+        // by delayMultiplier.
+        private val MOVE_LEFT_DELAYS = listOf(0, 1, 2, 3)
+        private val MOVE_RIGHT_DELAYS = listOf(1, 0, 3, 2)
+
+        // How much delay to apply to each subsequent digit. This is measured in terms of "fraction"
+        // (i.e. a value of 0.1 would cause a digit to wait until fraction had hit 0.1, or 0.2 etc
+        // before moving).
+        private const val MOVE_DIGIT_STEP = 0.1f
+
+        // Total available transition time for each digit, taking into account the step. If step is
+        // 0.1, then digit 0 would animate over 0.0 - 0.7, making availableTime 0.7.
+        private val AVAILABLE_ANIMATION_TIME = 1.0f - MOVE_DIGIT_STEP * (NUM_DIGITS - 1)
     }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index f03fee4..cd27263 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -21,7 +21,7 @@
 import android.os.UserHandle
 import android.provider.Settings
 import android.util.Log
-import com.android.systemui.dagger.qualifiers.Main
+import com.android.internal.annotations.Keep
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.plugins.ClockId
 import com.android.systemui.plugins.ClockMetadata
@@ -30,7 +30,6 @@
 import com.android.systemui.plugins.PluginListener
 import com.android.systemui.shared.plugins.PluginManager
 import com.google.gson.Gson
-import javax.inject.Inject
 
 private val TAG = ClockRegistry::class.simpleName
 private const val DEBUG = true
@@ -42,13 +41,6 @@
     val handler: Handler,
     defaultClockProvider: ClockProvider
 ) {
-    @Inject constructor(
-        context: Context,
-        pluginManager: PluginManager,
-        @Main handler: Handler,
-        defaultClockProvider: DefaultClockProvider
-    ) : this(context, pluginManager, handler, defaultClockProvider as ClockProvider) { }
-
     // Usually this would be a typealias, but a SAM provides better java interop
     fun interface ClockChangeListener {
         fun onClockChanged()
@@ -201,6 +193,7 @@
         val provider: ClockProvider
     )
 
+    @Keep
     private data class ClockSetting(
         val clockId: ClockId,
         val _applied_timestamp: Long?
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index b887951..da1d233 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -16,6 +16,7 @@
 import android.content.Context
 import android.content.res.Resources
 import android.graphics.Color
+import android.graphics.Rect
 import android.icu.text.NumberFormat
 import android.util.TypedValue
 import android.view.LayoutInflater
@@ -26,6 +27,7 @@
 import com.android.systemui.plugins.ClockEvents
 import com.android.systemui.plugins.ClockFaceController
 import com.android.systemui.plugins.ClockFaceEvents
+import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.shared.R
 import java.io.PrintWriter
 import java.util.Locale
@@ -85,9 +87,17 @@
         events.onTimeTick()
     }
 
+    override fun setLogBuffer(logBuffer: LogBuffer) {
+        smallClock.view.tag = "smallClockView"
+        largeClock.view.tag = "largeClockView"
+        smallClock.view.logBuffer = logBuffer
+        largeClock.view.logBuffer = logBuffer
+    }
+
     open inner class DefaultClockFaceController(
         override val view: AnimatableClockView,
     ) : ClockFaceController {
+
         // MAGENTA is a placeholder, and will be assigned correctly in initialize
         private var currentColor = Color.MAGENTA
         private var isRegionDark = false
@@ -130,6 +140,10 @@
             lp.topMargin = (-0.5f * view.bottom).toInt()
             view.setLayoutParams(lp)
         }
+
+        fun moveForSplitShade(fromRect: Rect, toRect: Rect, fraction: Float) {
+            view.moveForSplitShade(fromRect, toRect, fraction)
+        }
     }
 
     inner class DefaultClockEvents : ClockEvents {
@@ -209,6 +223,13 @@
                 clocks.forEach { it.animateDoze(dozeState.isActive, !hasJumped) }
             }
         }
+
+        override fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) {
+            largeClock.moveForSplitShade(fromRect, toRect, fraction)
+        }
+
+        override val hasCustomPositionUpdatedAnimation: Boolean
+            get() = true
     }
 
     private class AnimationState(
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index 72f8b7b..40c8774 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -1,13 +1,16 @@
 package com.android.systemui.shared.recents.utilities;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
 
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.view.Surface;
 
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.wm.shell.util.SplitBounds;
 
 /**
  * Utility class to position the thumbnail in the TaskView
@@ -16,10 +19,26 @@
 
     public static final float MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT = 0.1f;
 
+    /**
+     * Specifies that a stage is positioned at the top half of the screen if
+     * in portrait mode or at the left half of the screen if in landscape mode.
+     * TODO(b/254378592): Remove after consolidation
+     */
+    public static final int STAGE_POSITION_TOP_OR_LEFT = 0;
+
+    /**
+     * Specifies that a stage is positioned at the bottom half of the screen if
+     * in portrait mode or at the right half of the screen if in landscape mode.
+     * TODO(b/254378592): Remove after consolidation
+     */
+    public static final int STAGE_POSITION_BOTTOM_OR_RIGHT = 1;
+
     // Contains the portion of the thumbnail that is unclipped when fullscreen progress = 1.
     private final RectF mClippedInsets = new RectF();
     private final Matrix mMatrix = new Matrix();
     private boolean mIsOrientationChanged;
+    private SplitBounds mSplitBounds;
+    private int mDesiredStagePosition;
 
     public Matrix getMatrix() {
         return mMatrix;
@@ -33,6 +52,11 @@
         return mIsOrientationChanged;
     }
 
+    public void setSplitBounds(SplitBounds splitBounds, int desiredStagePosition) {
+        mSplitBounds = splitBounds;
+        mDesiredStagePosition = desiredStagePosition;
+    }
+
     /**
      * Updates the matrix based on the provided parameters
      */
@@ -42,10 +66,19 @@
         boolean isRotated = false;
         boolean isOrientationDifferent;
 
+        float fullscreenTaskWidth = screenWidthPx;
+        if (mSplitBounds != null && !mSplitBounds.appsStackedVertically) {
+            // For landscape, scale the width
+            float taskPercent = mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT
+                    ? mSplitBounds.leftTaskPercent
+                    : (1 - (mSplitBounds.leftTaskPercent + mSplitBounds.dividerWidthPercent));
+            // Scale landscape width to that of actual screen
+            fullscreenTaskWidth = screenWidthPx * taskPercent;
+        }
         int thumbnailRotation = thumbnailData.rotation;
         int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
         RectF thumbnailClipHint = new RectF();
-        float canvasScreenRatio = canvasWidth / (float) screenWidthPx;
+        float canvasScreenRatio = canvasWidth / fullscreenTaskWidth;
         float scaledTaskbarSize = taskbarSize * canvasScreenRatio;
         thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0;
 
@@ -180,7 +213,7 @@
      * portrait or vice versa, {@code false} otherwise
      */
     private boolean isOrientationChange(int deltaRotation) {
-        return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
+        return deltaRotation == ROTATION_90 || deltaRotation == ROTATION_270;
     }
 
     private void setThumbnailRotation(int deltaRotate, Rect thumbnailPosition) {
@@ -189,13 +222,13 @@
 
         mMatrix.setRotate(90 * deltaRotate);
         switch (deltaRotate) { /* Counter-clockwise */
-            case Surface.ROTATION_90:
+            case ROTATION_90:
                 translateX = thumbnailPosition.height();
                 break;
-            case Surface.ROTATION_270:
+            case ROTATION_270:
                 translateY = thumbnailPosition.width();
                 break;
-            case Surface.ROTATION_180:
+            case ROTATION_180:
                 translateX = thumbnailPosition.width();
                 translateY = thumbnailPosition.height();
                 break;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
index dd2e55d..cd4b999 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.shared.regionsampling
 
+import android.graphics.Color
 import android.graphics.Rect
 import android.view.View
 import androidx.annotation.VisibleForTesting
@@ -33,18 +34,19 @@
         regionSamplingEnabled: Boolean,
         updateFun: UpdateColorCallback
 ) {
-    private var isDark = RegionDarkness.DEFAULT
+    private var regionDarkness = RegionDarkness.DEFAULT
     private var samplingBounds = Rect()
     private val tmpScreenLocation = IntArray(2)
     @VisibleForTesting var regionSampler: RegionSamplingHelper? = null
-
+    private var lightForegroundColor = Color.WHITE
+    private var darkForegroundColor = Color.BLACK
     /**
      * Interface for method to be passed into RegionSamplingHelper
      */
     @FunctionalInterface
     interface UpdateColorCallback {
         /**
-         * Method to update the text colors after clock darkness changed.
+         * Method to update the foreground colors after clock darkness changed.
          */
         fun updateColors()
     }
@@ -59,6 +61,30 @@
         return RegionSamplingHelper(sampledView, callback, mainExecutor, bgExecutor)
     }
 
+    /**
+     * Sets the colors to be used for Dark and Light Foreground.
+     *
+     * @param lightColor The color used for Light Foreground.
+     * @param darkColor The color used for Dark Foreground.
+     */
+    fun setForegroundColors(lightColor: Int, darkColor: Int) {
+        lightForegroundColor = lightColor
+        darkForegroundColor = darkColor
+    }
+
+    /**
+     * Determines which foreground color to use based on region darkness.
+     *
+     * @return the determined foreground color
+     */
+    fun currentForegroundColor(): Int{
+        return if (regionDarkness.isDark) {
+            lightForegroundColor
+        } else {
+            darkForegroundColor
+        }
+    }
+
     private fun convertToClockDarkness(isRegionDark: Boolean): RegionDarkness {
         return if (isRegionDark) {
             RegionDarkness.DARK
@@ -68,7 +94,7 @@
     }
 
     fun currentRegionDarkness(): RegionDarkness {
-        return isDark
+        return regionDarkness
     }
 
     /**
@@ -97,7 +123,7 @@
             regionSampler = createRegionSamplingHelper(sampledView,
                     object : SamplingCallback {
                         override fun onRegionDarknessChanged(isRegionDark: Boolean) {
-                            isDark = convertToClockDarkness(isRegionDark)
+                            regionDarkness = convertToClockDarkness(isRegionDark)
                             updateFun.updateColors()
                         }
                         /**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index ce337bb..fd41cb06 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -194,12 +194,8 @@
                             Rect homeContentInsets, Rect minimizedHomeBounds) {
                         final RecentsAnimationControllerCompat controllerCompat =
                                 new RecentsAnimationControllerCompat(controller);
-                        final RemoteAnimationTargetCompat[] appsCompat =
-                                RemoteAnimationTargetCompat.wrap(apps);
-                        final RemoteAnimationTargetCompat[] wallpapersCompat =
-                                RemoteAnimationTargetCompat.wrap(wallpapers);
-                        animationHandler.onAnimationStart(controllerCompat, appsCompat,
-                                wallpapersCompat, homeContentInsets, minimizedHomeBounds);
+                        animationHandler.onAnimationStart(controllerCompat, apps,
+                                wallpapers, homeContentInsets, minimizedHomeBounds);
                     }
 
                     @Override
@@ -210,12 +206,7 @@
 
                     @Override
                     public void onTasksAppeared(RemoteAnimationTarget[] apps) {
-                        final RemoteAnimationTargetCompat[] compats =
-                                new RemoteAnimationTargetCompat[apps.length];
-                        for (int i = 0; i < apps.length; ++i) {
-                            compats[i] = new RemoteAnimationTargetCompat(apps[i]);
-                        }
-                        animationHandler.onTasksAppeared(compats);
+                        animationHandler.onTasksAppeared(apps);
                     }
                 };
             }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index 5d6598d..8a25096 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -51,6 +51,8 @@
             InteractionJankMonitor.CUJ_SPLIT_SCREEN_ENTER;
     public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION =
             InteractionJankMonitor.CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
+    public static final int CUJ_RECENTS_SCROLLING =
+            InteractionJankMonitor.CUJ_RECENTS_SCROLLING;
 
     @IntDef({
             CUJ_APP_LAUNCH_FROM_RECENTS,
@@ -59,7 +61,8 @@
             CUJ_APP_CLOSE_TO_PIP,
             CUJ_QUICK_SWITCH,
             CUJ_APP_LAUNCH_FROM_WIDGET,
-            CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION
+            CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
+            CUJ_RECENTS_SCROLLING
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index f2742b7..766266d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -110,6 +110,9 @@
     public static final int SYSUI_STATE_IMMERSIVE_MODE = 1 << 24;
     // The voice interaction session window is showing
     public static final int SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 25;
+    // Freeform windows are showing in desktop mode
+    public static final int SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1 << 26;
+
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({SYSUI_STATE_SCREEN_PINNING,
@@ -137,7 +140,8 @@
             SYSUI_STATE_BACK_DISABLED,
             SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED,
             SYSUI_STATE_IMMERSIVE_MODE,
-            SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING
+            SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING,
+            SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE
     })
     public @interface SystemUiStateFlags {}
 
@@ -173,6 +177,8 @@
                 ? "bubbles_mange_menu_expanded" : "");
         str.add((flags & SYSUI_STATE_IMMERSIVE_MODE) != 0 ? "immersive_mode" : "");
         str.add((flags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0 ? "vis_win_showing" : "");
+        str.add((flags & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0
+                ? "freeform_active_in_desktop_mode" : "");
         return str.toString();
     }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
index 5cca4a6..8bddf21 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
@@ -17,6 +17,7 @@
 package com.android.systemui.shared.system;
 
 import android.graphics.Rect;
+import android.view.RemoteAnimationTarget;
 
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
@@ -27,7 +28,7 @@
      * Called when the animation into Recents can start. This call is made on the binder thread.
      */
     void onAnimationStart(RecentsAnimationControllerCompat controller,
-            RemoteAnimationTargetCompat[] apps, RemoteAnimationTargetCompat[] wallpapers,
+            RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
             Rect homeContentInsets, Rect minimizedHomeBounds);
 
     /**
@@ -39,7 +40,7 @@
      * Called when the task of an activity that has been started while the recents animation
      * was running becomes ready for control.
      */
-    void onTasksAppeared(RemoteAnimationTargetCompat[] app);
+    void onTasksAppeared(RemoteAnimationTarget[] app);
 
     /**
      * Called to request that the current task tile be switched out for a screenshot (if not
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
index 09cf7c5..37e706a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
@@ -83,12 +83,6 @@
                     RemoteAnimationTarget[] wallpapers,
                     RemoteAnimationTarget[] nonApps,
                     final IRemoteAnimationFinishedCallback finishedCallback) {
-                final RemoteAnimationTargetCompat[] appsCompat =
-                        RemoteAnimationTargetCompat.wrap(apps);
-                final RemoteAnimationTargetCompat[] wallpapersCompat =
-                        RemoteAnimationTargetCompat.wrap(wallpapers);
-                final RemoteAnimationTargetCompat[] nonAppsCompat =
-                        RemoteAnimationTargetCompat.wrap(nonApps);
                 final Runnable animationFinishedCallback = new Runnable() {
                     @Override
                     public void run() {
@@ -100,8 +94,8 @@
                         }
                     }
                 };
-                remoteAnimationAdapter.onAnimationStart(transit, appsCompat, wallpapersCompat,
-                        nonAppsCompat, animationFinishedCallback);
+                remoteAnimationAdapter.onAnimationStart(transit, apps, wallpapers,
+                        nonApps, animationFinishedCallback);
             }
 
             @Override
@@ -121,12 +115,12 @@
                     SurfaceControl.Transaction t,
                     IRemoteTransitionFinishedCallback finishCallback) {
                 final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
-                final RemoteAnimationTargetCompat[] appsCompat =
+                final RemoteAnimationTarget[] apps =
                         RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
-                final RemoteAnimationTargetCompat[] wallpapersCompat =
+                final RemoteAnimationTarget[] wallpapers =
                         RemoteAnimationTargetCompat.wrapNonApps(
                                 info, true /* wallpapers */, t, leashMap);
-                final RemoteAnimationTargetCompat[] nonAppsCompat =
+                final RemoteAnimationTarget[] nonApps =
                         RemoteAnimationTargetCompat.wrapNonApps(
                                 info, false /* wallpapers */, t, leashMap);
 
@@ -189,9 +183,9 @@
                         }
                     }
                     // Make wallpaper visible immediately since launcher apparently won't do this.
-                    for (int i = wallpapersCompat.length - 1; i >= 0; --i) {
-                        t.show(wallpapersCompat[i].leash);
-                        t.setAlpha(wallpapersCompat[i].leash, 1.f);
+                    for (int i = wallpapers.length - 1; i >= 0; --i) {
+                        t.show(wallpapers[i].leash);
+                        t.setAlpha(wallpapers[i].leash, 1.f);
                     }
                 } else {
                     if (launcherTask != null) {
@@ -237,7 +231,7 @@
                 }
                 // TODO(bc-unlcok): Pass correct transit type.
                 remoteAnimationAdapter.onAnimationStart(TRANSIT_OLD_NONE,
-                        appsCompat, wallpapersCompat, nonAppsCompat, () -> {
+                        apps, wallpapers, nonApps, () -> {
                             synchronized (mFinishRunnables) {
                                 if (mFinishRunnables.remove(token) == null) return;
                             }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 0076292..5809c81 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -16,11 +16,12 @@
 
 package com.android.systemui.shared.system;
 
+import android.view.RemoteAnimationTarget;
 import android.view.WindowManager;
 
 public interface RemoteAnimationRunnerCompat {
     void onAnimationStart(@WindowManager.TransitionOldType int transit,
-            RemoteAnimationTargetCompat[] apps, RemoteAnimationTargetCompat[] wallpapers,
-            RemoteAnimationTargetCompat[] nonApps, Runnable finishedCallback);
+            RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
+            RemoteAnimationTarget[] nonApps, Runnable finishedCallback);
     void onAnimationCancelled();
 }
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 7c3b5fc..8d1768c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -11,12 +11,15 @@
  * 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.shared.system;
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.view.RemoteAnimationTarget.MODE_CHANGING;
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -29,88 +32,28 @@
 import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
+import android.app.TaskInfo;
 import android.app.WindowConfiguration;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.ArrayMap;
-import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.window.TransitionInfo;
+import android.window.TransitionInfo.Change;
 
 import java.util.ArrayList;
+import java.util.function.BiPredicate;
 
 /**
- * @see RemoteAnimationTarget
+ * Some utility methods for creating {@link RemoteAnimationTarget} instances.
  */
 public class RemoteAnimationTargetCompat {
 
-    public static final int MODE_OPENING = RemoteAnimationTarget.MODE_OPENING;
-    public static final int MODE_CLOSING = RemoteAnimationTarget.MODE_CLOSING;
-    public static final int MODE_CHANGING = RemoteAnimationTarget.MODE_CHANGING;
-    public final int mode;
-
-    public static final int ACTIVITY_TYPE_UNDEFINED = WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-    public static final int ACTIVITY_TYPE_STANDARD = WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-    public static final int ACTIVITY_TYPE_HOME = WindowConfiguration.ACTIVITY_TYPE_HOME;
-    public static final int ACTIVITY_TYPE_RECENTS = WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-    public static final int ACTIVITY_TYPE_ASSISTANT = WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-    public final int activityType;
-
-    public int taskId;
-    public final SurfaceControl leash;
-    public final boolean isTranslucent;
-    public final Rect clipRect;
-    public final int prefixOrderIndex;
-    public final Point position;
-    public final Rect localBounds;
-    public final Rect sourceContainerBounds;
-    public final Rect screenSpaceBounds;
-    public final Rect startScreenSpaceBounds;
-    public final boolean isNotInRecents;
-    public final Rect contentInsets;
-    public ActivityManager.RunningTaskInfo taskInfo;
-    public final boolean allowEnterPip;
-    public final int rotationChange;
-    public final int windowType;
-    public final WindowConfiguration windowConfiguration;
-
-    private final SurfaceControl mStartLeash;
-
-    // Fields used only to unwrap into RemoteAnimationTarget
-    private final Rect startBounds;
-
-    public final boolean willShowImeOnTarget;
-
-    public RemoteAnimationTargetCompat(RemoteAnimationTarget app) {
-        taskId = app.taskId;
-        mode = app.mode;
-        leash = app.leash;
-        isTranslucent = app.isTranslucent;
-        clipRect = app.clipRect;
-        position = app.position;
-        localBounds = app.localBounds;
-        sourceContainerBounds = app.sourceContainerBounds;
-        screenSpaceBounds = app.screenSpaceBounds;
-        startScreenSpaceBounds = screenSpaceBounds;
-        prefixOrderIndex = app.prefixOrderIndex;
-        isNotInRecents = app.isNotInRecents;
-        contentInsets = app.contentInsets;
-        activityType = app.windowConfiguration.getActivityType();
-        taskInfo = app.taskInfo;
-        allowEnterPip = app.allowEnterPip;
-        rotationChange = 0;
-
-        mStartLeash = app.startLeash;
-        windowType = app.windowType;
-        windowConfiguration = app.windowConfiguration;
-        startBounds = app.startBounds;
-        willShowImeOnTarget = app.willShowImeOnTarget;
-    }
-
     private static int newModeToLegacyMode(int newMode) {
         switch (newMode) {
             case WindowManager.TRANSIT_OPEN:
@@ -120,20 +63,10 @@
             case WindowManager.TRANSIT_TO_BACK:
                 return MODE_CLOSING;
             default:
-                return 2; // MODE_CHANGING
+                return MODE_CHANGING;
         }
     }
 
-    public RemoteAnimationTarget unwrap() {
-        final RemoteAnimationTarget target = new RemoteAnimationTarget(
-                taskId, mode, leash, isTranslucent, clipRect, contentInsets,
-                prefixOrderIndex, position, localBounds, screenSpaceBounds, windowConfiguration,
-                isNotInRecents, mStartLeash, startBounds, taskInfo, allowEnterPip, windowType
-        );
-        target.setWillShowImeOnTarget(willShowImeOnTarget);
-        return target;
-    }
-
     /**
      * Almost a copy of Transitions#setupStartState.
      * TODO: remove when there is proper cross-process transaction sync.
@@ -205,54 +138,61 @@
         return leashSurface;
     }
 
-    public RemoteAnimationTargetCompat(TransitionInfo.Change change, int order,
-            TransitionInfo info, SurfaceControl.Transaction t) {
-        mode = newModeToLegacyMode(change.getMode());
+    /**
+     * Creates a new RemoteAnimationTarget from the provided change info
+     */
+    public static RemoteAnimationTarget newTarget(TransitionInfo.Change change, int order,
+            TransitionInfo info, SurfaceControl.Transaction t,
+            @Nullable ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
+        int taskId;
+        boolean isNotInRecents;
+        ActivityManager.RunningTaskInfo taskInfo;
+        WindowConfiguration windowConfiguration;
+
         taskInfo = change.getTaskInfo();
         if (taskInfo != null) {
             taskId = taskInfo.taskId;
             isNotInRecents = !taskInfo.isRunning;
-            activityType = taskInfo.getActivityType();
             windowConfiguration = taskInfo.configuration.windowConfiguration;
         } else {
             taskId = INVALID_TASK_ID;
             isNotInRecents = true;
-            activityType = ACTIVITY_TYPE_UNDEFINED;
             windowConfiguration = new WindowConfiguration();
         }
 
-        // TODO: once we can properly sync transactions across process, then get rid of this leash.
-        leash = createLeash(info, change, order, t);
-
-        isTranslucent = (change.getFlags() & TransitionInfo.FLAG_TRANSLUCENT) != 0;
-        clipRect = null;
-        position = null;
-        localBounds = new Rect(change.getEndAbsBounds());
+        Rect localBounds = new Rect(change.getEndAbsBounds());
         localBounds.offsetTo(change.getEndRelOffset().x, change.getEndRelOffset().y);
-        sourceContainerBounds = null;
-        screenSpaceBounds = new Rect(change.getEndAbsBounds());
-        startScreenSpaceBounds = new Rect(change.getStartAbsBounds());
 
-        prefixOrderIndex = order;
-        // TODO(shell-transitions): I guess we need to send content insets? evaluate how its used.
-        contentInsets = new Rect(0, 0, 0, 0);
-        allowEnterPip = change.getAllowEnterPip();
-        mStartLeash = null;
-        rotationChange = change.getEndRotation() - change.getStartRotation();
-        windowType = (change.getFlags() & FLAG_IS_DIVIDER_BAR) != 0
-                ? TYPE_DOCK_DIVIDER : INVALID_WINDOW_TYPE;
-
-        startBounds = change.getStartAbsBounds();
-        willShowImeOnTarget = (change.getFlags() & TransitionInfo.FLAG_WILL_IME_SHOWN) != 0;
-    }
-
-    public static RemoteAnimationTargetCompat[] wrap(RemoteAnimationTarget[] apps) {
-        final int length = apps != null ? apps.length : 0;
-        final RemoteAnimationTargetCompat[] appsCompat = new RemoteAnimationTargetCompat[length];
-        for (int i = 0; i < length; i++) {
-            appsCompat[i] = new RemoteAnimationTargetCompat(apps[i]);
+        RemoteAnimationTarget target = new RemoteAnimationTarget(
+                taskId,
+                newModeToLegacyMode(change.getMode()),
+                // TODO: once we can properly sync transactions across process,
+                // then get rid of this leash.
+                createLeash(info, change, order, t),
+                (change.getFlags() & TransitionInfo.FLAG_TRANSLUCENT) != 0,
+                null,
+                // TODO(shell-transitions): we need to send content insets? evaluate how its used.
+                new Rect(0, 0, 0, 0),
+                order,
+                null,
+                localBounds,
+                new Rect(change.getEndAbsBounds()),
+                windowConfiguration,
+                isNotInRecents,
+                null,
+                new Rect(change.getStartAbsBounds()),
+                taskInfo,
+                change.getAllowEnterPip(),
+                (change.getFlags() & FLAG_IS_DIVIDER_BAR) != 0
+                        ? TYPE_DOCK_DIVIDER : INVALID_WINDOW_TYPE
+        );
+        target.setWillShowImeOnTarget(
+                (change.getFlags() & TransitionInfo.FLAG_WILL_IME_SHOWN) != 0);
+        target.setRotationChange(change.getEndRotation() - change.getStartRotation());
+        if (leashMap != null) {
+            leashMap.put(change.getLeash(), target.leash);
         }
-        return appsCompat;
+        return target;
     }
 
     /**
@@ -261,35 +201,20 @@
      * @param leashMap Temporary map of change leash -> launcher leash. Is an output, so should be
      *                 populated by this function. If null, it is ignored.
      */
-    public static RemoteAnimationTargetCompat[] wrapApps(TransitionInfo info,
+    public static RemoteAnimationTarget[] wrapApps(TransitionInfo info,
             SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
-        final ArrayList<RemoteAnimationTargetCompat> out = new ArrayList<>();
-        final SparseArray<TransitionInfo.Change> childTaskTargets = new SparseArray<>();
-        for (int i = 0; i < info.getChanges().size(); i++) {
-            final TransitionInfo.Change change = info.getChanges().get(i);
-            if (change.getTaskInfo() == null) continue;
-
-            final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+        SparseBooleanArray childTaskTargets = new SparseBooleanArray();
+        return wrap(info, t, leashMap, (change, taskInfo) -> {
             // Children always come before parent since changes are in top-to-bottom z-order.
-            if (taskInfo != null) {
-                if (childTaskTargets.contains(taskInfo.taskId)) {
-                    // has children, so not a leaf. Skip.
-                    continue;
-                }
-                if (taskInfo.hasParentTask()) {
-                    childTaskTargets.put(taskInfo.parentTaskId, change);
-                }
+            if ((taskInfo == null) || childTaskTargets.get(taskInfo.taskId)) {
+                // has children, so not a leaf. Skip.
+                return false;
             }
-
-            final RemoteAnimationTargetCompat targetCompat =
-                    new RemoteAnimationTargetCompat(change, info.getChanges().size() - i, info, t);
-            if (leashMap != null) {
-                leashMap.put(change.getLeash(), targetCompat.leash);
+            if (taskInfo.hasParentTask()) {
+                childTaskTargets.put(taskInfo.parentTaskId, true);
             }
-            out.add(targetCompat);
-        }
-
-        return out.toArray(new RemoteAnimationTargetCompat[out.size()]);
+            return true;
+        });
     }
 
     /**
@@ -300,38 +225,22 @@
      * @param leashMap Temporary map of change leash -> launcher leash. Is an output, so should be
      *                 populated by this function. If null, it is ignored.
      */
-    public static RemoteAnimationTargetCompat[] wrapNonApps(TransitionInfo info, boolean wallpapers,
+    public static RemoteAnimationTarget[] wrapNonApps(TransitionInfo info, boolean wallpapers,
             SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
-        final ArrayList<RemoteAnimationTargetCompat> out = new ArrayList<>();
-
-        for (int i = 0; i < info.getChanges().size(); i++) {
-            final TransitionInfo.Change change = info.getChanges().get(i);
-            if (change.getTaskInfo() != null) continue;
-
-            final boolean changeIsWallpaper =
-                    (change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0;
-            if (wallpapers != changeIsWallpaper) continue;
-
-            final RemoteAnimationTargetCompat targetCompat =
-                    new RemoteAnimationTargetCompat(change, info.getChanges().size() - i, info, t);
-            if (leashMap != null) {
-                leashMap.put(change.getLeash(), targetCompat.leash);
-            }
-            out.add(targetCompat);
-        }
-
-        return out.toArray(new RemoteAnimationTargetCompat[out.size()]);
+        return wrap(info, t, leashMap, (change, taskInfo) -> (taskInfo == null)
+                && wallpapers == ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0));
     }
 
-    /**
-     * @see SurfaceControl#release()
-     */
-    public void release() {
-        if (leash != null) {
-            leash.release();
+    private static RemoteAnimationTarget[] wrap(TransitionInfo info,
+            SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap,
+            BiPredicate<Change, TaskInfo> filter) {
+        final ArrayList<RemoteAnimationTarget> out = new ArrayList<>();
+        for (int i = 0; i < info.getChanges().size(); i++) {
+            TransitionInfo.Change change = info.getChanges().get(i);
+            if (filter.test(change, change.getTaskInfo())) {
+                out.add(newTarget(change, info.getChanges().size() - i, info, t, leashMap));
+            }
         }
-        if (mStartLeash != null) {
-            mStartLeash.release();
-        }
+        return out.toArray(new RemoteAnimationTarget[out.size()]);
     }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index f679225..d6655a7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -17,7 +17,9 @@
 package com.android.systemui.shared.system;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
@@ -27,8 +29,7 @@
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.window.TransitionFilter.CONTAINER_ORDER_TOP;
 
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_RECENTS;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.newTarget;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -45,6 +46,7 @@
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.IRecentsAnimationController;
+import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.window.IRemoteTransition;
 import android.window.IRemoteTransitionFinishedCallback;
@@ -127,9 +129,9 @@
                     SurfaceControl.Transaction t,
                     IRemoteTransitionFinishedCallback finishedCallback) {
                 final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
-                final RemoteAnimationTargetCompat[] apps =
+                final RemoteAnimationTarget[] apps =
                         RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
-                final RemoteAnimationTargetCompat[] wallpapers =
+                final RemoteAnimationTarget[] wallpapers =
                         RemoteAnimationTargetCompat.wrapNonApps(
                                 info, true /* wallpapers */, t, leashMap);
                 // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
@@ -230,7 +232,7 @@
         private PictureInPictureSurfaceTransaction mPipTransaction = null;
         private IBinder mTransition = null;
         private boolean mKeyguardLocked = false;
-        private RemoteAnimationTargetCompat[] mAppearedTargets;
+        private RemoteAnimationTarget[] mAppearedTargets;
         private boolean mWillFinishToHome = false;
 
         void setup(RecentsAnimationControllerCompat wrapped, TransitionInfo info,
@@ -325,18 +327,15 @@
             final int layer = mInfo.getChanges().size() * 3;
             mOpeningLeashes = new ArrayList<>();
             mOpeningHome = cancelRecents;
-            final RemoteAnimationTargetCompat[] targets =
-                    new RemoteAnimationTargetCompat[openingTasks.size()];
+            final RemoteAnimationTarget[] targets =
+                    new RemoteAnimationTarget[openingTasks.size()];
             for (int i = 0; i < openingTasks.size(); ++i) {
                 final TransitionInfo.Change change = openingTasks.valueAt(i);
                 mOpeningLeashes.add(change.getLeash());
                 // We are receiving new opening tasks, so convert to onTasksAppeared.
-                final RemoteAnimationTargetCompat target = new RemoteAnimationTargetCompat(
-                        change, layer, info, t);
-                mLeashMap.put(mOpeningLeashes.get(i), target.leash);
-                t.reparent(target.leash, mInfo.getRootLeash());
-                t.setLayer(target.leash, layer);
-                targets[i] = target;
+                targets[i] = newTarget(change, layer, info, t, mLeashMap);
+                t.reparent(targets[i].leash, mInfo.getRootLeash());
+                t.setLayer(targets[i].leash, layer);
             }
             t.apply();
             mAppearedTargets = targets;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
deleted file mode 100644
index 30c062b..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
+++ /dev/null
@@ -1,380 +0,0 @@
-/*
- * Copyright (C) 2018 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.shared.system;
-
-import android.graphics.HardwareRenderer;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.Handler.Callback;
-import android.os.Message;
-import android.os.Trace;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
-import android.view.View;
-import android.view.ViewRootImpl;
-
-import java.util.function.Consumer;
-
-/**
- * Helper class to apply surface transactions in sync with RenderThread.
- *
- * NOTE: This is a modification of {@link android.view.SyncRtSurfaceTransactionApplier}, we can't 
- *       currently reference that class from the shared lib as it is hidden.
- */
-public class SyncRtSurfaceTransactionApplierCompat {
-
-    public static final int FLAG_ALL = 0xffffffff;
-    public static final int FLAG_ALPHA = 1;
-    public static final int FLAG_MATRIX = 1 << 1;
-    public static final int FLAG_WINDOW_CROP = 1 << 2;
-    public static final int FLAG_LAYER = 1 << 3;
-    public static final int FLAG_CORNER_RADIUS = 1 << 4;
-    public static final int FLAG_BACKGROUND_BLUR_RADIUS = 1 << 5;
-    public static final int FLAG_VISIBILITY = 1 << 6;
-    public static final int FLAG_RELATIVE_LAYER = 1 << 7;
-    public static final int FLAG_SHADOW_RADIUS = 1 << 8;
-
-    private static final int MSG_UPDATE_SEQUENCE_NUMBER = 0;
-
-    private final SurfaceControl mBarrierSurfaceControl;
-    private final ViewRootImpl mTargetViewRootImpl;
-    private final Handler mApplyHandler;
-
-    private int mSequenceNumber = 0;
-    private int mPendingSequenceNumber = 0;
-    private Runnable mAfterApplyCallback;
-
-    /**
-     * @param targetView The view in the surface that acts as synchronization anchor.
-     */
-    public SyncRtSurfaceTransactionApplierCompat(View targetView) {
-        mTargetViewRootImpl = targetView != null ? targetView.getViewRootImpl() : null;
-        mBarrierSurfaceControl = mTargetViewRootImpl != null
-            ? mTargetViewRootImpl.getSurfaceControl() : null;
-
-        mApplyHandler = new Handler(new Callback() {
-            @Override
-            public boolean handleMessage(Message msg) {
-                if (msg.what == MSG_UPDATE_SEQUENCE_NUMBER) {
-                    onApplyMessage(msg.arg1);
-                    return true;
-                }
-                return false;
-            }
-        });
-    }
-
-    private void onApplyMessage(int seqNo) {
-        mSequenceNumber = seqNo;
-        if (mSequenceNumber == mPendingSequenceNumber && mAfterApplyCallback != null) {
-            Runnable r = mAfterApplyCallback;
-            mAfterApplyCallback = null;
-            r.run();
-        }
-    }
-
-    /**
-     * Schedules applying surface parameters on the next frame.
-     *
-     * @param params The surface parameters to apply. DO NOT MODIFY the list after passing into
-     *               this method to avoid synchronization issues.
-     */
-    public void scheduleApply(final SyncRtSurfaceTransactionApplierCompat.SurfaceParams... params) {
-        if (mTargetViewRootImpl == null || mTargetViewRootImpl.getView() == null) {
-            return;
-        }
-
-        mPendingSequenceNumber++;
-        final int toApplySeqNo = mPendingSequenceNumber;
-        mTargetViewRootImpl.registerRtFrameCallback(new HardwareRenderer.FrameDrawingCallback() {
-            @Override
-            public void onFrameDraw(long frame) {
-                if (mBarrierSurfaceControl == null || !mBarrierSurfaceControl.isValid()) {
-                    Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0)
-                            .sendToTarget();
-                    return;
-                }
-                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Sync transaction frameNumber=" + frame);
-                Transaction t = new Transaction();
-                for (int i = params.length - 1; i >= 0; i--) {
-                    SyncRtSurfaceTransactionApplierCompat.SurfaceParams surfaceParams =
-                            params[i];
-                    surfaceParams.applyTo(t);
-                }
-                if (mTargetViewRootImpl != null) {
-                    mTargetViewRootImpl.mergeWithNextTransaction(t, frame);
-                } else {
-                    t.apply();
-                }
-                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
-                Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0)
-                        .sendToTarget();
-            }
-        });
-
-        // Make sure a frame gets scheduled.
-        mTargetViewRootImpl.getView().invalidate();
-    }
-
-    /**
-     * Calls the runnable when any pending apply calls have completed
-     */
-    public void addAfterApplyCallback(final Runnable afterApplyCallback) {
-        if (mSequenceNumber == mPendingSequenceNumber) {
-            afterApplyCallback.run();
-        } else {
-            if (mAfterApplyCallback == null) {
-                mAfterApplyCallback = afterApplyCallback;
-            } else {
-                final Runnable oldCallback = mAfterApplyCallback;
-                mAfterApplyCallback = new Runnable() {
-                    @Override
-                    public void run() {
-                        afterApplyCallback.run();
-                        oldCallback.run();
-                    }
-                };
-            }
-        }
-    }
-
-    public static void applyParams(TransactionCompat t,
-            SyncRtSurfaceTransactionApplierCompat.SurfaceParams params) {
-        params.applyTo(t.mTransaction);
-    }
-
-    /**
-     * Creates an instance of SyncRtSurfaceTransactionApplier, deferring until the target view is
-     * attached if necessary.
-     */
-    public static void create(final View targetView,
-            final Consumer<SyncRtSurfaceTransactionApplierCompat> callback) {
-        if (targetView == null) {
-            // No target view, no applier
-            callback.accept(null);
-        } else if (targetView.getViewRootImpl() != null) {
-            // Already attached, we're good to go
-            callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView));
-        } else {
-            // Haven't been attached before we can get the view root
-            targetView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
-                @Override
-                public void onViewAttachedToWindow(View v) {
-                    targetView.removeOnAttachStateChangeListener(this);
-                    callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView));
-                }
-
-                @Override
-                public void onViewDetachedFromWindow(View v) {
-                    // Do nothing
-                }
-            });
-        }
-    }
-
-    public static class SurfaceParams {
-        public static class Builder {
-            final SurfaceControl surface;
-            int flags;
-            float alpha;
-            float cornerRadius;
-            int backgroundBlurRadius;
-            Matrix matrix;
-            Rect windowCrop;
-            int layer;
-            SurfaceControl relativeTo;
-            int relativeLayer;
-            boolean visible;
-            float shadowRadius;
-
-            /**
-             * @param surface The surface to modify.
-             */
-            public Builder(SurfaceControl surface) {
-                this.surface = surface;
-            }
-
-            /**
-             * @param alpha The alpha value to apply to the surface.
-             * @return this Builder
-             */
-            public Builder withAlpha(float alpha) {
-                this.alpha = alpha;
-                flags |= FLAG_ALPHA;
-                return this;
-            }
-
-            /**
-             * @param matrix The matrix to apply to the surface.
-             * @return this Builder
-             */
-            public Builder withMatrix(Matrix matrix) {
-                this.matrix = new Matrix(matrix);
-                flags |= FLAG_MATRIX;
-                return this;
-            }
-
-            /**
-             * @param windowCrop The window crop to apply to the surface.
-             * @return this Builder
-             */
-            public Builder withWindowCrop(Rect windowCrop) {
-                this.windowCrop = new Rect(windowCrop);
-                flags |= FLAG_WINDOW_CROP;
-                return this;
-            }
-
-            /**
-             * @param layer The layer to assign the surface.
-             * @return this Builder
-             */
-            public Builder withLayer(int layer) {
-                this.layer = layer;
-                flags |= FLAG_LAYER;
-                return this;
-            }
-
-            /**
-             * @param relativeTo The surface that's set relative layer to.
-             * @param relativeLayer The relative layer.
-             * @return this Builder
-             */
-            public Builder withRelativeLayerTo(SurfaceControl relativeTo, int relativeLayer) {
-                this.relativeTo = relativeTo;
-                this.relativeLayer = relativeLayer;
-                flags |= FLAG_RELATIVE_LAYER;
-                return this;
-            }
-
-            /**
-             * @param radius the Radius for rounded corners to apply to the surface.
-             * @return this Builder
-             */
-            public Builder withCornerRadius(float radius) {
-                this.cornerRadius = radius;
-                flags |= FLAG_CORNER_RADIUS;
-                return this;
-            }
-
-            /**
-             * @param radius the Radius for the shadows to apply to the surface.
-             * @return this Builder
-             */
-            public Builder withShadowRadius(float radius) {
-                this.shadowRadius = radius;
-                flags |= FLAG_SHADOW_RADIUS;
-                return this;
-            }
-
-            /**
-             * @param radius the Radius for blur to apply to the background surfaces.
-             * @return this Builder
-             */
-            public Builder withBackgroundBlur(int radius) {
-                this.backgroundBlurRadius = radius;
-                flags |= FLAG_BACKGROUND_BLUR_RADIUS;
-                return this;
-            }
-
-            /**
-             * @param visible The visibility to apply to the surface.
-             * @return this Builder
-             */
-            public Builder withVisibility(boolean visible) {
-                this.visible = visible;
-                flags |= FLAG_VISIBILITY;
-                return this;
-            }
-
-            /**
-             * @return a new SurfaceParams instance
-             */
-            public SurfaceParams build() {
-                return new SurfaceParams(surface, flags, alpha, matrix, windowCrop, layer,
-                        relativeTo, relativeLayer, cornerRadius, backgroundBlurRadius, visible,
-                        shadowRadius);
-            }
-        }
-
-        private SurfaceParams(SurfaceControl surface, int flags, float alpha, Matrix matrix,
-                Rect windowCrop, int layer, SurfaceControl relativeTo, int relativeLayer,
-                float cornerRadius, int backgroundBlurRadius, boolean visible, float shadowRadius) {
-            this.flags = flags;
-            this.surface = surface;
-            this.alpha = alpha;
-            this.matrix = matrix;
-            this.windowCrop = windowCrop;
-            this.layer = layer;
-            this.relativeTo = relativeTo;
-            this.relativeLayer = relativeLayer;
-            this.cornerRadius = cornerRadius;
-            this.backgroundBlurRadius = backgroundBlurRadius;
-            this.visible = visible;
-            this.shadowRadius = shadowRadius;
-        }
-
-        private final int flags;
-        private final float[] mTmpValues = new float[9];
-
-        public final SurfaceControl surface;
-        public final float alpha;
-        public final float cornerRadius;
-        public final int backgroundBlurRadius;
-        public final Matrix matrix;
-        public final Rect windowCrop;
-        public final int layer;
-        public final SurfaceControl relativeTo;
-        public final int relativeLayer;
-        public final boolean visible;
-        public final float shadowRadius;
-
-        public void applyTo(SurfaceControl.Transaction t) {
-            if ((flags & FLAG_MATRIX) != 0) {
-                t.setMatrix(surface, matrix, mTmpValues);
-            }
-            if ((flags & FLAG_WINDOW_CROP) != 0) {
-                t.setWindowCrop(surface, windowCrop);
-            }
-            if ((flags & FLAG_ALPHA) != 0) {
-                t.setAlpha(surface, alpha);
-            }
-            if ((flags & FLAG_LAYER) != 0) {
-                t.setLayer(surface, layer);
-            }
-            if ((flags & FLAG_CORNER_RADIUS) != 0) {
-                t.setCornerRadius(surface, cornerRadius);
-            }
-            if ((flags & FLAG_BACKGROUND_BLUR_RADIUS) != 0) {
-                t.setBackgroundBlurRadius(surface, backgroundBlurRadius);
-            }
-            if ((flags & FLAG_VISIBILITY) != 0) {
-                if (visible) {
-                    t.show(surface);
-                } else {
-                    t.hide(surface);
-                }
-            }
-            if ((flags & FLAG_RELATIVE_LAYER) != 0) {
-                t.setRelativeLayer(surface, relativeTo, relativeLayer);
-            }
-            if ((flags & FLAG_SHADOW_RADIUS) != 0) {
-                t.setShadowRadius(surface, shadowRadius);
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
deleted file mode 100644
index 43a882a5..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2018 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.shared.system;
-
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
-
-public class TransactionCompat {
-
-    final Transaction mTransaction;
-
-    final float[] mTmpValues = new float[9];
-
-    public TransactionCompat() {
-        mTransaction = new Transaction();
-    }
-
-    public void apply() {
-        mTransaction.apply();
-    }
-
-    public TransactionCompat show(SurfaceControl surfaceControl) {
-        mTransaction.show(surfaceControl);
-        return this;
-    }
-
-    public TransactionCompat hide(SurfaceControl surfaceControl) {
-        mTransaction.hide(surfaceControl);
-        return this;
-    }
-
-    public TransactionCompat setPosition(SurfaceControl surfaceControl, float x, float y) {
-        mTransaction.setPosition(surfaceControl, x, y);
-        return this;
-    }
-
-    public TransactionCompat setSize(SurfaceControl surfaceControl, int w, int h) {
-        mTransaction.setBufferSize(surfaceControl, w, h);
-        return this;
-    }
-
-    public TransactionCompat setLayer(SurfaceControl surfaceControl, int z) {
-        mTransaction.setLayer(surfaceControl, z);
-        return this;
-    }
-
-    public TransactionCompat setAlpha(SurfaceControl surfaceControl, float alpha) {
-        mTransaction.setAlpha(surfaceControl, alpha);
-        return this;
-    }
-
-    public TransactionCompat setOpaque(SurfaceControl surfaceControl, boolean opaque) {
-        mTransaction.setOpaque(surfaceControl, opaque);
-        return this;
-    }
-
-    public TransactionCompat setMatrix(SurfaceControl surfaceControl, float dsdx, float dtdx,
-            float dtdy, float dsdy) {
-        mTransaction.setMatrix(surfaceControl, dsdx, dtdx, dtdy, dsdy);
-        return this;
-    }
-
-    public TransactionCompat setMatrix(SurfaceControl surfaceControl, Matrix matrix) {
-        mTransaction.setMatrix(surfaceControl, matrix, mTmpValues);
-        return this;
-    }
-
-    public TransactionCompat setWindowCrop(SurfaceControl surfaceControl, Rect crop) {
-        mTransaction.setWindowCrop(surfaceControl, crop);
-        return this;
-    }
-
-    public TransactionCompat setCornerRadius(SurfaceControl surfaceControl, float radius) {
-        mTransaction.setCornerRadius(surfaceControl, radius);
-        return this;
-    }
-
-    public TransactionCompat setBackgroundBlurRadius(SurfaceControl surfaceControl, int radius) {
-        mTransaction.setBackgroundBlurRadius(surfaceControl, radius);
-        return this;
-    }
-
-    public TransactionCompat setColor(SurfaceControl surfaceControl, float[] color) {
-        mTransaction.setColor(surfaceControl, color);
-        return this;
-    }
-
-    public static void setRelativeLayer(Transaction t, SurfaceControl surfaceControl,
-            SurfaceControl relativeTo, int z) {
-        t.setRelativeLayer(surfaceControl, relativeTo, z);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
deleted file mode 100644
index 6064be9..0000000
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.icu.text.NumberFormat;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.settingslib.Utils;
-import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.clocks.AnimatableClockView;
-import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.util.ViewController;
-
-import java.io.PrintWriter;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.TimeZone;
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-
-/**
- * Controller for an AnimatableClockView on the keyguard. Instantiated by
- * {@link KeyguardClockSwitchController}.
- */
-public class AnimatableClockController extends ViewController<AnimatableClockView> {
-    private static final String TAG = "AnimatableClockCtrl";
-    private static final int FORMAT_NUMBER = 1234567890;
-
-    private final StatusBarStateController mStatusBarStateController;
-    private final BroadcastDispatcher mBroadcastDispatcher;
-    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private final BatteryController mBatteryController;
-    private final int mDozingColor = Color.WHITE;
-    private Optional<RegionSamplingHelper> mRegionSamplingHelper = Optional.empty();
-    private Rect mSamplingBounds = new Rect();
-    private int mLockScreenColor;
-    private final boolean mRegionSamplingEnabled;
-
-    private boolean mIsDozing;
-    private boolean mIsCharging;
-    private float mDozeAmount;
-    boolean mKeyguardShowing;
-    private Locale mLocale;
-
-    private final NumberFormat mBurmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my"));
-    private final String mBurmeseNumerals;
-    private final float mBurmeseLineSpacing;
-    private final float mDefaultLineSpacing;
-
-    @Inject
-    public AnimatableClockController(
-            AnimatableClockView view,
-            StatusBarStateController statusBarStateController,
-            BroadcastDispatcher broadcastDispatcher,
-            BatteryController batteryController,
-            KeyguardUpdateMonitor keyguardUpdateMonitor,
-            @Main Resources resources,
-            @Main Executor mainExecutor,
-            @Background Executor bgExecutor,
-            FeatureFlags featureFlags
-    ) {
-        super(view);
-        mStatusBarStateController = statusBarStateController;
-        mBroadcastDispatcher = broadcastDispatcher;
-        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-        mBatteryController = batteryController;
-
-        mBurmeseNumerals = mBurmeseNf.format(FORMAT_NUMBER);
-        mBurmeseLineSpacing = resources.getFloat(
-                R.dimen.keyguard_clock_line_spacing_scale_burmese);
-        mDefaultLineSpacing = resources.getFloat(
-                R.dimen.keyguard_clock_line_spacing_scale);
-
-        mRegionSamplingEnabled = featureFlags.isEnabled(Flags.REGION_SAMPLING);
-        if (!mRegionSamplingEnabled) {
-            return;
-        }
-
-        mRegionSamplingHelper = Optional.of(new RegionSamplingHelper(mView,
-                new RegionSamplingHelper.SamplingCallback() {
-                    @Override
-                    public void onRegionDarknessChanged(boolean isRegionDark) {
-                        if (isRegionDark) {
-                            mLockScreenColor = Color.WHITE;
-                        } else {
-                            mLockScreenColor = Color.BLACK;
-                        }
-                        initColors();
-                    }
-
-                    @Override
-                    public Rect getSampledRegion(View sampledView) {
-                        mSamplingBounds = new Rect(sampledView.getLeft(), sampledView.getTop(),
-                                sampledView.getRight(), sampledView.getBottom());
-                        return mSamplingBounds;
-                    }
-
-                    @Override
-                    public boolean isSamplingEnabled() {
-                        return mRegionSamplingEnabled;
-                    }
-                }, mainExecutor, bgExecutor)
-        );
-        mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
-            regionSamplingHelper.setWindowVisible(true);
-        });
-    }
-
-    private void reset() {
-        mView.animateDoze(mIsDozing, false);
-    }
-
-    private final BatteryController.BatteryStateChangeCallback mBatteryCallback =
-            new BatteryController.BatteryStateChangeCallback() {
-        @Override
-        public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
-            if (mKeyguardShowing && !mIsCharging && charging) {
-                mView.animateCharge(mStatusBarStateController::isDozing);
-            }
-            mIsCharging = charging;
-        }
-    };
-
-    private final BroadcastReceiver mLocaleBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            updateLocale();
-        }
-    };
-
-    private final StatusBarStateController.StateListener mStatusBarStateListener =
-            new StatusBarStateController.StateListener() {
-                @Override
-                public void onDozeAmountChanged(float linear, float eased) {
-                    boolean noAnimation = (mDozeAmount == 0f && linear == 1f)
-                            || (mDozeAmount == 1f && linear == 0f);
-                    boolean isDozing = linear > mDozeAmount;
-                    mDozeAmount = linear;
-                    if (mIsDozing != isDozing) {
-                        mIsDozing = isDozing;
-                        mView.animateDoze(mIsDozing, !noAnimation);
-                    }
-                }
-            };
-
-    private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
-            new KeyguardUpdateMonitorCallback() {
-        @Override
-        public void onKeyguardVisibilityChanged(boolean showing) {
-            mKeyguardShowing = showing;
-            if (!mKeyguardShowing) {
-                // reset state (ie: after weight animations)
-                reset();
-            }
-        }
-
-        @Override
-        public void onTimeFormatChanged(String timeFormat) {
-            mView.refreshFormat();
-        }
-
-        @Override
-        public void onTimeZoneChanged(TimeZone timeZone) {
-            mView.onTimeZoneChanged(timeZone);
-        }
-
-        @Override
-        public void onUserSwitchComplete(int userId) {
-            mView.refreshFormat();
-        }
-    };
-
-    @Override
-    protected void onInit() {
-        mIsDozing = mStatusBarStateController.isDozing();
-    }
-
-    @Override
-    protected void onViewAttached() {
-        updateLocale();
-        mBroadcastDispatcher.registerReceiver(mLocaleBroadcastReceiver,
-                new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
-        mDozeAmount = mStatusBarStateController.getDozeAmount();
-        mIsDozing = mStatusBarStateController.isDozing() || mDozeAmount != 0;
-        mBatteryController.addCallback(mBatteryCallback);
-        mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
-
-        mStatusBarStateController.addCallback(mStatusBarStateListener);
-
-        mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
-            regionSamplingHelper.start(mSamplingBounds);
-        });
-
-        mView.onTimeZoneChanged(TimeZone.getDefault());
-        initColors();
-        mView.animateDoze(mIsDozing, false);
-    }
-
-    @Override
-    protected void onViewDetached() {
-        mBroadcastDispatcher.unregisterReceiver(mLocaleBroadcastReceiver);
-        mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
-        mBatteryController.removeCallback(mBatteryCallback);
-        mStatusBarStateController.removeCallback(mStatusBarStateListener);
-        mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
-            regionSamplingHelper.stop();
-        });
-    }
-
-    /** Animate the clock appearance */
-    public void animateAppear() {
-        if (!mIsDozing) mView.animateAppearOnLockscreen();
-    }
-
-    /** Animate the clock appearance when a foldable device goes from fully-open/half-open state to
-     * fully folded state and it goes to sleep (always on display screen) */
-    public void animateFoldAppear() {
-        mView.animateFoldAppear(true);
-    }
-
-    /**
-     * Updates the time for the view.
-     */
-    public void refreshTime() {
-        mView.refreshTime();
-    }
-
-    /**
-     * Return locallly stored dozing state.
-     */
-    @VisibleForTesting
-    public boolean isDozing() {
-        return mIsDozing;
-    }
-
-    private void updateLocale() {
-        Locale currLocale = Locale.getDefault();
-        if (!Objects.equals(currLocale, mLocale)) {
-            mLocale = currLocale;
-            NumberFormat nf = NumberFormat.getInstance(mLocale);
-            if (nf.format(FORMAT_NUMBER).equals(mBurmeseNumerals)) {
-                mView.setLineSpacingScale(mBurmeseLineSpacing);
-            } else {
-                mView.setLineSpacingScale(mDefaultLineSpacing);
-            }
-            mView.refreshFormat();
-        }
-    }
-
-    private void initColors() {
-        if (!mRegionSamplingEnabled) {
-            mLockScreenColor = Utils.getColorAttrDefaultColor(getContext(),
-                    com.android.systemui.R.attr.wallpaperTextColorAccent);
-        }
-        mView.setColors(mDozingColor, mLockScreenColor);
-        mView.animateDoze(mIsDozing, false);
-    }
-
-    /**
-     * Dump information for debugging
-     */
-    public void dump(@NonNull PrintWriter pw) {
-        pw.println(this);
-        mView.dump(pw);
-        mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
-            regionSamplingHelper.dump(pw);
-        });
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
index 0075ddd..450784e 100644
--- a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
+++ b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
@@ -16,19 +16,29 @@
 
 package com.android.keyguard
 
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
 import android.content.Context
 import android.content.res.ColorStateList
 import android.content.res.TypedArray
 import android.graphics.Color
 import android.util.AttributeSet
+import android.view.View
 import com.android.settingslib.Utils
+import com.android.systemui.animation.Interpolators
 
 /** Displays security messages for the keyguard bouncer. */
-class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) :
+open class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) :
     KeyguardMessageArea(context, attrs) {
     private val DEFAULT_COLOR = -1
     private var mDefaultColorState: ColorStateList? = null
     private var mNextMessageColorState: ColorStateList? = ColorStateList.valueOf(DEFAULT_COLOR)
+    private val animatorSet = AnimatorSet()
+    private var textAboutToShow: CharSequence? = null
+    protected open val SHOW_DURATION_MILLIS = 150L
+    protected open val HIDE_DURATION_MILLIS = 200L
 
     override fun updateTextColor() {
         var colorState = mDefaultColorState
@@ -58,4 +68,46 @@
         mDefaultColorState = Utils.getColorAttr(context, android.R.attr.textColorPrimary)
         super.reloadColor()
     }
+
+    override fun setMessage(msg: CharSequence?) {
+        if ((msg == textAboutToShow && msg != null) || msg == text) {
+            return
+        }
+        textAboutToShow = msg
+
+        if (animatorSet.isRunning) {
+            animatorSet.cancel()
+            textAboutToShow = null
+        }
+
+        val hideAnimator =
+            ObjectAnimator.ofFloat(this, View.ALPHA, 1f, 0f).apply {
+                duration = HIDE_DURATION_MILLIS
+                interpolator = Interpolators.STANDARD_ACCELERATE
+            }
+
+        hideAnimator.addListener(
+            object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator?) {
+                    super@BouncerKeyguardMessageArea.setMessage(msg)
+                }
+            }
+        )
+        val showAnimator =
+            ObjectAnimator.ofFloat(this, View.ALPHA, 0f, 1f).apply {
+                duration = SHOW_DURATION_MILLIS
+                interpolator = Interpolators.STANDARD_DECELERATE
+            }
+
+        showAnimator.addListener(
+            object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator?) {
+                    textAboutToShow = null
+                }
+            }
+        )
+
+        animatorSet.playSequentially(hideAnimator, showAnimator)
+        animatorSet.start()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 9151238..386c095 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -23,12 +23,20 @@
 import android.text.format.DateFormat
 import android.util.TypedValue
 import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.REGION_SAMPLING
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.log.dagger.KeyguardClockLog
 import com.android.systemui.plugins.ClockController
-import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.shared.regionsampling.RegionSamplingInstance
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
@@ -38,13 +46,20 @@
 import java.util.TimeZone
 import java.util.concurrent.Executor
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
 
 /**
  * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
  * [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController].
  */
 open class ClockEventController @Inject constructor(
-    private val statusBarStateController: StatusBarStateController,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val broadcastDispatcher: BroadcastDispatcher,
     private val batteryController: BatteryController,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@@ -53,12 +68,14 @@
     private val context: Context,
     @Main private val mainExecutor: Executor,
     @Background private val bgExecutor: Executor,
-    private val featureFlags: FeatureFlags,
+    @KeyguardClockLog private val logBuffer: LogBuffer,
+    private val featureFlags: FeatureFlags
 ) {
     var clock: ClockController? = null
         set(value) {
             field = value
             if (value != null) {
+                value.setLogBuffer(logBuffer)
                 value.initialize(resources, dozeAmount, 0f)
                 updateRegionSamplers(value)
             }
@@ -70,9 +87,9 @@
     private var isCharging = false
     private var dozeAmount = 0f
     private var isKeyguardVisible = false
-
-    private val regionSamplingEnabled =
-            featureFlags.isEnabled(com.android.systemui.flags.Flags.REGION_SAMPLING)
+    private var isRegistered = false
+    private var disposableHandle: DisposableHandle? = null
+    private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING)
 
     private fun updateColors() {
         if (regionSamplingEnabled && smallRegionSampler != null && largeRegionSampler != null) {
@@ -165,15 +182,6 @@
         }
     }
 
-    private val statusBarStateListener = object : StatusBarStateController.StateListener {
-        override fun onDozeAmountChanged(linear: Float, eased: Float) {
-            clock?.animations?.doze(linear)
-
-            isDozing = linear > dozeAmount
-            dozeAmount = linear
-        }
-    }
-
     private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
         override fun onKeyguardVisibilityChanged(visible: Boolean) {
             isKeyguardVisible = visible
@@ -195,13 +203,11 @@
         }
     }
 
-    init {
-        isDozing = statusBarStateController.isDozing
-    }
-
-    fun registerListeners() {
-        dozeAmount = statusBarStateController.dozeAmount
-        isDozing = statusBarStateController.isDozing || dozeAmount != 0f
+    fun registerListeners(parent: View) {
+        if (isRegistered) {
+            return
+        }
+        isRegistered = true
 
         broadcastDispatcher.registerReceiver(
             localeBroadcastReceiver,
@@ -210,17 +216,28 @@
         configurationController.addCallback(configListener)
         batteryController.addCallback(batteryCallback)
         keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
-        statusBarStateController.addCallback(statusBarStateListener)
         smallRegionSampler?.startRegionSampler()
         largeRegionSampler?.startRegionSampler()
+        disposableHandle = parent.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                listenForDozing(this)
+                listenForDozeAmount(this)
+                listenForDozeAmountTransition(this)
+            }
+        }
     }
 
     fun unregisterListeners() {
+        if (!isRegistered) {
+            return
+        }
+        isRegistered = false
+
+        disposableHandle?.dispose()
         broadcastDispatcher.unregisterReceiver(localeBroadcastReceiver)
         configurationController.removeCallback(configListener)
         batteryController.removeCallback(batteryCallback)
         keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
-        statusBarStateController.removeCallback(statusBarStateListener)
         smallRegionSampler?.stopRegionSampler()
         largeRegionSampler?.stopRegionSampler()
     }
@@ -235,8 +252,39 @@
         largeRegionSampler?.dump(pw)
     }
 
-    companion object {
-        private val TAG = ClockEventController::class.simpleName
-        private const val FORMAT_NUMBER = 1234567890
+    @VisibleForTesting
+    internal fun listenForDozeAmount(scope: CoroutineScope): Job {
+        return scope.launch {
+            keyguardInteractor.dozeAmount.collect {
+                dozeAmount = it
+                clock?.animations?.doze(dozeAmount)
+            }
+        }
+    }
+
+    @VisibleForTesting
+    internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
+        return scope.launch {
+            keyguardTransitionInteractor.aodToLockscreenTransition.collect {
+                // Would eventually run this:
+                // dozeAmount = it.value
+                // clock?.animations?.doze(dozeAmount)
+            }
+        }
+    }
+
+    @VisibleForTesting
+    internal fun listenForDozing(scope: CoroutineScope): Job {
+        return scope.launch {
+            combine (
+                keyguardInteractor.dozeAmount,
+                keyguardInteractor.isDozing,
+            ) { localDozeAmount, localIsDozing ->
+                localDozeAmount > dozeAmount || localIsDozing
+            }
+            .collect { localIsDozing ->
+                isDozing = localIsDozing
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index d03ef98..8ebad6c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -127,7 +127,7 @@
         if (useLargeClock) {
             out = mSmallClockFrame;
             in = mLargeClockFrame;
-            if (indexOfChild(in) == -1) addView(in);
+            if (indexOfChild(in) == -1) addView(in, 0);
             direction = -1;
             statusAreaYTranslation = mSmallClockFrame.getTop() - mStatusArea.getTop()
                     + mSmartspaceTopOffset;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index b450ec3..35eecdf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -40,6 +40,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.plugins.ClockAnimations;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.clocks.ClockRegistry;
@@ -164,7 +165,7 @@
     protected void onViewAttached() {
         mClockRegistry.registerClockChangeListener(mClockChangedListener);
         setClock(mClockRegistry.createCurrentClock());
-        mClockEventController.registerListeners();
+        mClockEventController.registerListeners(mView);
         mKeyguardClockTopMargin =
                 mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
 
@@ -404,5 +405,10 @@
             clock.dump(pw);
         }
     }
+
+    /** Gets the animations for the current clock. */
+    public ClockAnimations getClockAnimations() {
+        return getClock().getAnimations();
+    }
 }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index f26b905..73229c3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -152,6 +152,7 @@
     }
 
     public void startAppearAnimation() {
+        mMessageAreaController.setMessage(getInitialMessageResId());
         mView.startAppearAnimation();
     }
 
@@ -169,6 +170,11 @@
         return view.indexOfChild(mView);
     }
 
+    /** Determines the message to show in the bouncer when it first appears. */
+    protected int getInitialMessageResId() {
+        return 0;
+    }
+
     /** Factory for a {@link KeyguardInputViewController}. */
     public static class Factory {
         private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index c2802f7..2bd3ca5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -18,7 +18,6 @@
 
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
-import android.text.TextUtils;
 
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -100,15 +99,6 @@
         mView.setMessage(resId);
     }
 
-    /**
-     * Set Text if KeyguardMessageArea is empty.
-     */
-    public void setMessageIfEmpty(int resId) {
-        if (TextUtils.isEmpty(mView.getText())) {
-            setMessage(resId);
-        }
-    }
-
     public void setNextMessageColor(ColorStateList colorState) {
         mView.setNextMessageColor(colorState);
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 29e912f..0025986 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -187,7 +187,7 @@
     @Override
     void resetState() {
         mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
-        mMessageAreaController.setMessage("");
+        mMessageAreaController.setMessage(getInitialMessageResId());
         final boolean wasDisabled = mPasswordEntry.isEnabled();
         mView.setPasswordEntryEnabled(true);
         mView.setPasswordEntryInputEnabled(true);
@@ -207,7 +207,6 @@
         if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
             showInput();
         }
-        mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_password);
     }
 
     private void showInput() {
@@ -324,4 +323,9 @@
                 //enabled input method subtype (The current IME should be LatinIME.)
                 || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
     }
+
+    @Override
+    protected int getInitialMessageResId() {
+        return R.string.keyguard_enter_your_password;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 9871645..1f0bd54 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -298,12 +298,6 @@
     }
 
     @Override
-    public void onResume(int reason) {
-        super.onResume(reason);
-        mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pattern);
-    }
-
-    @Override
     public boolean needsInput() {
         return false;
     }
@@ -361,7 +355,7 @@
     }
 
     private void displayDefaultSecurityMessage() {
-        mMessageAreaController.setMessage("");
+        mMessageAreaController.setMessage(getInitialMessageResId());
     }
 
     private void handleAttemptLockout(long elapsedRealtimeDeadline) {
@@ -392,4 +386,9 @@
 
         }.start();
     }
+
+    @Override
+    protected int getInitialMessageResId() {
+        return R.string.keyguard_enter_your_pattern;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 59a018a..f7423ed 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -127,7 +127,6 @@
     public void onResume(int reason) {
         super.onResume(reason);
         mPasswordEntry.requestFocus();
-        mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 89fcc47..7876f07 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -76,20 +76,13 @@
     }
 
     @Override
-    void resetState() {
-        super.resetState();
-        mMessageAreaController.setMessage("");
-    }
-
-    @Override
-    public void startAppearAnimation() {
-        mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin);
-        super.startAppearAnimation();
-    }
-
-    @Override
     public boolean startDisappearAnimation(Runnable finishRunnable) {
         return mView.startDisappearAnimation(
                 mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
     }
+
+    @Override
+    protected int getInitialMessageResId() {
+        return R.string.keyguard_enter_your_pin;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index bcd1a1e..81305f9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -219,13 +219,16 @@
     };
 
 
-    private SwipeListener mSwipeListener = new SwipeListener() {
+    private final SwipeListener mSwipeListener = new SwipeListener() {
         @Override
         public void onSwipeUp() {
             if (!mUpdateMonitor.isFaceDetectionRunning()) {
-                mUpdateMonitor.requestFaceAuth(true, FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
+                boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(true,
+                        FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
                 mKeyguardSecurityCallback.userActivity();
-                showMessage(null, null);
+                if (didFaceAuthRun) {
+                    showMessage(null, null);
+                }
             }
             if (mUpdateMonitor.isFaceEnrolled()) {
                 mUpdateMonitor.requestActiveUnlock(
@@ -234,7 +237,7 @@
             }
         }
     };
-    private ConfigurationController.ConfigurationListener mConfigurationListener =
+    private final ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
                 @Override
                 public void onThemeChanged() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index e9f06ed..7849747 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -20,6 +20,7 @@
 import android.util.Slog;
 
 import com.android.keyguard.KeyguardClockSwitch.ClockSize;
+import com.android.systemui.plugins.ClockAnimations;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -232,4 +233,9 @@
             mView.setClipBounds(null);
         }
     }
+
+    /** Gets the animations for the current clock. */
+    public ClockAnimations getClockAnimations() {
+        return mKeyguardClockSwitchController.getClockAnimations();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 8792a21..cb1330d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1428,6 +1428,16 @@
         }
     }
 
+    private void notifyNonStrongBiometricStateChanged(int userId) {
+        Assert.isMainThread();
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onNonStrongBiometricAllowedChanged(userId);
+            }
+        }
+    }
+
     private void dispatchErrorMessage(CharSequence message) {
         Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -1778,11 +1788,14 @@
 
     public static class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
         private final Consumer<Integer> mStrongAuthRequiredChangedCallback;
+        private final Consumer<Integer> mNonStrongBiometricAllowedChanged;
 
         public StrongAuthTracker(Context context,
-                Consumer<Integer> strongAuthRequiredChangedCallback) {
+                Consumer<Integer> strongAuthRequiredChangedCallback,
+                Consumer<Integer> nonStrongBiometricAllowedChanged) {
             super(context);
             mStrongAuthRequiredChangedCallback = strongAuthRequiredChangedCallback;
+            mNonStrongBiometricAllowedChanged = nonStrongBiometricAllowedChanged;
         }
 
         public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
@@ -1800,6 +1813,14 @@
         public void onStrongAuthRequiredChanged(int userId) {
             mStrongAuthRequiredChangedCallback.accept(userId);
         }
+
+        // TODO(b/247091681): Renaming the inappropriate onIsNonStrongBiometricAllowedChanged
+        //  callback wording for Weak/Convenience idle timeout constraint that only allow
+        //  Strong-Auth
+        @Override
+        public void onIsNonStrongBiometricAllowedChanged(int userId) {
+            mNonStrongBiometricAllowedChanged.accept(userId);
+        }
     }
 
     protected void handleStartedWakingUp() {
@@ -1948,7 +1969,8 @@
         mSubscriptionManager = subscriptionManager;
         mTelephonyListenerManager = telephonyListenerManager;
         mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
-        mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged);
+        mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged,
+                this::notifyNonStrongBiometricStateChanged);
         mBackgroundExecutor = backgroundExecutor;
         mBroadcastDispatcher = broadcastDispatcher;
         mInteractionJankMonitor = interactionJankMonitor;
@@ -2344,11 +2366,13 @@
      * @param userInitiatedRequest true if the user explicitly requested face auth
      * @param reason One of the reasons {@link FaceAuthApiRequestReason} on why this API is being
      * invoked.
+     * @return current face auth detection state, true if it is running.
      */
-    public void requestFaceAuth(boolean userInitiatedRequest,
+    public boolean requestFaceAuth(boolean userInitiatedRequest,
             @FaceAuthApiRequestReason String reason) {
         mLogger.logFaceAuthRequested(userInitiatedRequest, reason);
         updateFaceListeningState(BIOMETRIC_ACTION_START, apiRequestReasonToUiEvent(reason));
+        return isFaceDetectionRunning();
     }
 
     /**
@@ -2358,10 +2382,6 @@
         stopListeningForFace(FACE_AUTH_STOPPED_USER_INPUT_ON_BOUNCER);
     }
 
-    public boolean isFaceScanning() {
-        return mFaceRunningState == BIOMETRIC_STATE_RUNNING;
-    }
-
     private void updateFaceListeningState(int action, @NonNull FaceAuthUiEvent faceAuthUiEvent) {
         // If this message exists, we should not authenticate again until this message is
         // consumed by the handler
@@ -2409,7 +2429,7 @@
      * Attempts to trigger active unlock from trust agent.
      */
     private void requestActiveUnlock(
-            ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
+            @NonNull ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
             String reason,
             boolean dismissKeyguard
     ) {
@@ -2439,7 +2459,7 @@
      * Only dismisses the keyguard under certain conditions.
      */
     public void requestActiveUnlock(
-            ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
+            @NonNull ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
             String extraReason
     ) {
         final boolean canFaceBypass = isFaceEnrolled() && mKeyguardBypassController != null
@@ -2706,7 +2726,7 @@
         return shouldListen;
     }
 
-    private void maybeLogListenerModelData(KeyguardListenModel model) {
+    private void maybeLogListenerModelData(@NonNull KeyguardListenModel model) {
         mLogger.logKeyguardListenerModel(model);
 
         if (model instanceof KeyguardActiveUnlockModel) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index bc5ab88..c06e1dc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -291,4 +291,9 @@
      * Called when the notification shade is expanded or collapsed.
      */
     public void onShadeExpandedChanged(boolean expanded) { }
+
+    /**
+     * Called when the non-strong biometric state changed.
+     */
+    public void onNonStrongBiometricAllowedChanged(int userId) { }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockInfoModule.java
similarity index 86%
rename from packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
rename to packages/SystemUI/src/com/android/keyguard/clock/ClockInfoModule.java
index c4be1ba535..72a44bd 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockInfoModule.java
@@ -21,9 +21,14 @@
 import dagger.Module;
 import dagger.Provides;
 
-/** Dagger Module for clock package. */
+/**
+ * Dagger Module for clock package.
+ *
+ * @deprecated Migrate to ClockRegistry
+ */
 @Module
-public abstract class ClockModule {
+@Deprecated
+public abstract class ClockInfoModule {
 
     /** */
     @Provides
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
new file mode 100644
index 0000000..f43f559
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -0,0 +1,45 @@
+/*
+ * 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.keyguard.dagger;
+
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.shared.clocks.ClockRegistry;
+import com.android.systemui.shared.clocks.DefaultClockProvider;
+import com.android.systemui.shared.plugins.PluginManager;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Dagger Module for clocks. */
+@Module
+public abstract class ClockRegistryModule {
+    /** Provide the ClockRegistry as a singleton so that it is not instantiated more than once. */
+    @Provides
+    @SysUISingleton
+    public static ClockRegistry getClockRegistry(
+            @Application Context context,
+            PluginManager pluginManager,
+            @Main Handler handler,
+            DefaultClockProvider defaultClockProvider) {
+        return new ClockRegistry(context, pluginManager, handler, defaultClockProvider);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
index 2c2ab7b..6264ce7 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
@@ -17,9 +17,9 @@
 package com.android.keyguard.logging
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
 import com.android.systemui.log.dagger.BiometricMessagesLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
 import javax.inject.Inject
 
 /** Helper class for logging for [com.android.systemui.biometrics.FaceHelpMessageDeferral] */
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 50012a5..46f3d4e 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -16,15 +16,15 @@
 
 package com.android.keyguard.logging
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.VERBOSE
-import com.android.systemui.log.LogLevel.WARNING
-import com.android.systemui.log.MessageInitializer
-import com.android.systemui.log.MessagePrinter
 import com.android.systemui.log.dagger.KeyguardLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.ERROR
+import com.android.systemui.plugins.log.LogLevel.VERBOSE
+import com.android.systemui.plugins.log.LogLevel.WARNING
+import com.android.systemui.plugins.log.MessageInitializer
+import com.android.systemui.plugins.log.MessagePrinter
 import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 2eee957..2f79e30 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -22,13 +22,13 @@
 import com.android.keyguard.ActiveUnlockConfig
 import com.android.keyguard.KeyguardListenModel
 import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.VERBOSE
-import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.ERROR
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogLevel.VERBOSE
+import com.android.systemui.plugins.log.LogLevel.WARNING
 import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
 import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
@@ -51,7 +51,7 @@
 
     fun log(@CompileTimeConstant msg: String, level: LogLevel) = logBuffer.log(TAG, level, msg)
 
-    fun logActiveUnlockTriggered(reason: String) {
+    fun logActiveUnlockTriggered(reason: String?) {
         logBuffer.log("ActiveUnlock", DEBUG,
                 { str1 = reason },
                 { "initiate active unlock triggerReason=$str1" })
@@ -101,14 +101,14 @@
                 { "Face authenticated for wrong user: $int1" })
     }
 
-    fun logFaceAuthHelpMsg(msgId: Int, helpMsg: String) {
+    fun logFaceAuthHelpMsg(msgId: Int, helpMsg: String?) {
         logBuffer.log(TAG, DEBUG, {
                     int1 = msgId
                     str1 = helpMsg
                 }, { "Face help received, msgId: $int1 msg: $str1" })
     }
 
-    fun logFaceAuthRequested(userInitiatedRequest: Boolean, reason: String) {
+    fun logFaceAuthRequested(userInitiatedRequest: Boolean, reason: String?) {
         logBuffer.log(TAG, DEBUG, {
             bool1 = userInitiatedRequest
             str1 = reason
@@ -187,7 +187,7 @@
                 { "No Profile Owner or Device Owner supervision app found for User $int1" })
     }
 
-    fun logPhoneStateChanged(newState: String) {
+    fun logPhoneStateChanged(newState: String?) {
         logBuffer.log(TAG, DEBUG,
                 { str1 = newState },
                 { "handlePhoneStateChanged($str1)" })
@@ -240,7 +240,7 @@
         }, { "handleServiceStateChange(subId=$int1, serviceState=$str1)" })
     }
 
-    fun logServiceStateIntent(action: String, serviceState: ServiceState?, subId: Int) {
+    fun logServiceStateIntent(action: String?, serviceState: ServiceState?, subId: Int) {
         logBuffer.log(TAG, VERBOSE, {
             str1 = action
             str2 = "$serviceState"
@@ -256,7 +256,7 @@
         }, { "handleSimStateChange(subId=$int1, slotId=$int2, state=$long1)" })
     }
 
-    fun logSimStateFromIntent(action: String, extraSimState: String, slotId: Int, subId: Int) {
+    fun logSimStateFromIntent(action: String?, extraSimState: String?, slotId: Int, subId: Int) {
         logBuffer.log(TAG, VERBOSE, {
             str1 = action
             str2 = extraSimState
@@ -289,7 +289,7 @@
                 { "SubInfo:$str1" })
     }
 
-    fun logTimeFormatChanged(newTimeFormat: String) {
+    fun logTimeFormatChanged(newTimeFormat: String?) {
         logBuffer.log(TAG, DEBUG,
                 { str1 = newTimeFormat },
                 { "handleTimeFormatUpdate timeFormat=$str1" })
@@ -338,18 +338,18 @@
 
     fun logUserRequestedUnlock(
         requestOrigin: ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN,
-        reason: String,
+        reason: String?,
         dismissKeyguard: Boolean
     ) {
         logBuffer.log("ActiveUnlock", DEBUG, {
-                    str1 = requestOrigin.name
+                    str1 = requestOrigin?.name
                     str2 = reason
                     bool1 = dismissKeyguard
                 }, { "reportUserRequestedUnlock origin=$str1 reason=$str2 dismissKeyguard=$bool1" })
     }
 
     fun logShowTrustGrantedMessage(
-            message: String
+            message: String?
     ) {
         logBuffer.log(TAG, DEBUG, {
             str1 = message
diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
index a3351e1..5d52056 100644
--- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
+++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
@@ -86,30 +86,38 @@
         onUpdate()
     }
 
-    fun onDisplayChanged(newDisplayUniqueId: String?) {
+    fun updateConfiguration(newDisplayUniqueId: String?) {
+        val info = DisplayInfo()
+        context.display?.getDisplayInfo(info)
         val oldMode: Display.Mode? = displayMode
-        val display: Display? = context.display
-        displayMode = display?.mode
+        displayMode = info.mode
 
-        if (displayUniqueId != display?.uniqueId) {
-            displayUniqueId = display?.uniqueId
-            shouldDrawCutout = DisplayCutout.getFillBuiltInDisplayCutout(
-                context.resources, displayUniqueId
-            )
-        }
+        updateDisplayUniqueId(info.uniqueId)
 
         // Skip if display mode or cutout hasn't changed.
         if (!displayModeChanged(oldMode, displayMode) &&
-                display?.cutout == displayInfo.displayCutout) {
+                displayInfo.displayCutout == info.displayCutout &&
+                displayRotation == info.rotation) {
             return
         }
-        if (newDisplayUniqueId == display?.uniqueId) {
+        if (newDisplayUniqueId == info.uniqueId) {
+            displayRotation = info.rotation
             updateCutout()
             updateProtectionBoundingPath()
             onUpdate()
         }
     }
 
+    open fun updateDisplayUniqueId(newDisplayUniqueId: String?) {
+        if (displayUniqueId != newDisplayUniqueId) {
+            displayUniqueId = newDisplayUniqueId
+            shouldDrawCutout = DisplayCutout.getFillBuiltInDisplayCutout(
+                    context.resources, displayUniqueId
+            )
+            invalidate()
+        }
+    }
+
     open fun updateRotation(rotation: Int) {
         displayRotation = rotation
         updateCutout()
diff --git a/packages/SystemUI/src/com/android/systemui/Dumpable.java b/packages/SystemUI/src/com/android/systemui/Dumpable.java
index 6525951..73fdce6 100644
--- a/packages/SystemUI/src/com/android/systemui/Dumpable.java
+++ b/packages/SystemUI/src/com/android/systemui/Dumpable.java
@@ -30,7 +30,6 @@
 
     /**
      * Called when it's time to dump the internal state
-     * @param fd A file descriptor.
      * @param pw Where to write your dump to.
      * @param args Arguments.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index c595586..3e0fa45 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -19,6 +19,7 @@
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
 import android.animation.AnimatorSet
+import android.animation.TimeInterpolator
 import android.animation.ValueAnimator
 import android.content.Context
 import android.graphics.Canvas
@@ -55,7 +56,7 @@
     private val rimRect = RectF()
     private var cameraProtectionColor = Color.BLACK
     var faceScanningAnimColor = Utils.getColorAttrDefaultColor(context,
-            com.android.systemui.R.attr.wallpaperTextColorAccent)
+            R.attr.wallpaperTextColorAccent)
     private var cameraProtectionAnimator: ValueAnimator? = null
     var hideOverlayRunnable: Runnable? = null
     var faceAuthSucceeded = false
@@ -84,46 +85,19 @@
     }
 
     override fun drawCutoutProtection(canvas: Canvas) {
-        if (rimProgress > HIDDEN_RIM_SCALE && !protectionRect.isEmpty) {
-            val rimPath = Path(protectionPath)
-            val scaleMatrix = Matrix().apply {
-                val rimBounds = RectF()
-                rimPath.computeBounds(rimBounds, true)
-                setScale(rimProgress, rimProgress, rimBounds.centerX(), rimBounds.centerY())
-            }
-            rimPath.transform(scaleMatrix)
-            rimPaint.style = Paint.Style.FILL
-            val rimPaintAlpha = rimPaint.alpha
-            rimPaint.color = ColorUtils.blendARGB(
-                    faceScanningAnimColor,
-                    Color.WHITE,
-                    statusBarStateController.dozeAmount)
-            rimPaint.alpha = rimPaintAlpha
-            canvas.drawPath(rimPath, rimPaint)
+        if (protectionRect.isEmpty) {
+            return
         }
-
-        if (cameraProtectionProgress > HIDDEN_CAMERA_PROTECTION_SCALE &&
-                !protectionRect.isEmpty) {
-            val scaledProtectionPath = Path(protectionPath)
-            val scaleMatrix = Matrix().apply {
-                val protectionPathRect = RectF()
-                scaledProtectionPath.computeBounds(protectionPathRect, true)
-                setScale(cameraProtectionProgress, cameraProtectionProgress,
-                        protectionPathRect.centerX(), protectionPathRect.centerY())
-            }
-            scaledProtectionPath.transform(scaleMatrix)
-            paint.style = Paint.Style.FILL
-            paint.color = cameraProtectionColor
-            canvas.drawPath(scaledProtectionPath, paint)
+        if (rimProgress > HIDDEN_RIM_SCALE) {
+            drawFaceScanningRim(canvas)
         }
-    }
-
-    override fun updateVisOnUpdateCutout(): Boolean {
-        return false // instead, we always update the visibility whenever face scanning starts/ends
+        if (cameraProtectionProgress > HIDDEN_CAMERA_PROTECTION_SCALE) {
+            drawCameraProtection(canvas)
+        }
     }
 
     override fun enableShowProtection(show: Boolean) {
-        val showScanningAnimNow = keyguardUpdateMonitor.isFaceScanning && show
+        val showScanningAnimNow = keyguardUpdateMonitor.isFaceDetectionRunning && show
         if (showScanningAnimNow == showScanningAnim) {
             return
         }
@@ -152,91 +126,26 @@
                     if (showScanningAnim) Interpolators.STANDARD_ACCELERATE
                     else if (faceAuthSucceeded) Interpolators.STANDARD
                     else Interpolators.STANDARD_DECELERATE
-            addUpdateListener(ValueAnimator.AnimatorUpdateListener {
-                animation: ValueAnimator ->
-                cameraProtectionProgress = animation.animatedValue as Float
-                invalidate()
-            })
+            addUpdateListener(this@FaceScanningOverlay::updateCameraProtectionProgress)
             addListener(object : AnimatorListenerAdapter() {
                 override fun onAnimationEnd(animation: Animator) {
                     cameraProtectionAnimator = null
                     if (!showScanningAnim) {
-                        visibility = View.INVISIBLE
-                        hideOverlayRunnable?.run()
-                        hideOverlayRunnable = null
-                        requestLayout()
+                        hide()
                     }
                 }
             })
         }
 
         rimAnimator?.cancel()
-        rimAnimator = AnimatorSet().apply {
-            if (showScanningAnim) {
-                val rimAppearAnimator = ValueAnimator.ofFloat(SHOW_CAMERA_PROTECTION_SCALE,
-                        PULSE_RADIUS_OUT).apply {
-                    duration = PULSE_APPEAR_DURATION
-                    interpolator = Interpolators.STANDARD_DECELERATE
-                    addUpdateListener(ValueAnimator.AnimatorUpdateListener {
-                        animation: ValueAnimator ->
-                        rimProgress = animation.animatedValue as Float
-                        invalidate()
-                    })
-                }
-
-                // animate in camera protection, rim, and then pulse in/out
-                playSequentially(cameraProtectionAnimator, rimAppearAnimator,
-                        createPulseAnimator(), createPulseAnimator(),
-                        createPulseAnimator(), createPulseAnimator(),
-                        createPulseAnimator(), createPulseAnimator())
-            } else {
-                val rimDisappearAnimator = ValueAnimator.ofFloat(
-                        rimProgress,
-                        if (faceAuthSucceeded) PULSE_RADIUS_SUCCESS
-                        else SHOW_CAMERA_PROTECTION_SCALE
-                ).apply {
-                    duration =
-                            if (faceAuthSucceeded) PULSE_SUCCESS_DISAPPEAR_DURATION
-                            else PULSE_ERROR_DISAPPEAR_DURATION
-                    interpolator =
-                            if (faceAuthSucceeded) Interpolators.STANDARD_DECELERATE
-                            else Interpolators.STANDARD
-                    addUpdateListener(ValueAnimator.AnimatorUpdateListener {
-                        animation: ValueAnimator ->
-                        rimProgress = animation.animatedValue as Float
-                        invalidate()
-                    })
-                    addListener(object : AnimatorListenerAdapter() {
-                        override fun onAnimationEnd(animation: Animator) {
-                            rimProgress = HIDDEN_RIM_SCALE
-                            invalidate()
-                        }
-                    })
-                }
-                if (faceAuthSucceeded) {
-                    val successOpacityAnimator = ValueAnimator.ofInt(255, 0).apply {
-                        duration = PULSE_SUCCESS_DISAPPEAR_DURATION
-                        interpolator = Interpolators.LINEAR
-                        addUpdateListener(ValueAnimator.AnimatorUpdateListener {
-                            animation: ValueAnimator ->
-                            rimPaint.alpha = animation.animatedValue as Int
-                            invalidate()
-                        })
-                        addListener(object : AnimatorListenerAdapter() {
-                            override fun onAnimationEnd(animation: Animator) {
-                                rimPaint.alpha = 255
-                                invalidate()
-                            }
-                        })
-                    }
-                    val rimSuccessAnimator = AnimatorSet()
-                    rimSuccessAnimator.playTogether(rimDisappearAnimator, successOpacityAnimator)
-                    playTogether(rimSuccessAnimator, cameraProtectionAnimator)
-                } else {
-                    playTogether(rimDisappearAnimator, cameraProtectionAnimator)
-                }
-            }
-
+        rimAnimator = if (showScanningAnim) {
+            createFaceScanningRimAnimator()
+        } else if (faceAuthSucceeded) {
+            createFaceSuccessRimAnimator()
+        } else {
+            createFaceNotSuccessRimAnimator()
+        }
+        rimAnimator?.apply {
             addListener(object : AnimatorListenerAdapter() {
                 override fun onAnimationEnd(animation: Animator) {
                     rimAnimator = null
@@ -245,34 +154,12 @@
                     }
                 }
             })
-            start()
         }
+        rimAnimator?.start()
     }
 
-    fun createPulseAnimator(): AnimatorSet {
-        return AnimatorSet().apply {
-            val pulseInwards = ValueAnimator.ofFloat(
-                    PULSE_RADIUS_OUT, PULSE_RADIUS_IN).apply {
-                duration = PULSE_DURATION_INWARDS
-                interpolator = Interpolators.STANDARD
-                addUpdateListener(ValueAnimator.AnimatorUpdateListener {
-                    animation: ValueAnimator ->
-                    rimProgress = animation.animatedValue as Float
-                    invalidate()
-                })
-            }
-            val pulseOutwards = ValueAnimator.ofFloat(
-                    PULSE_RADIUS_IN, PULSE_RADIUS_OUT).apply {
-                duration = PULSE_DURATION_OUTWARDS
-                interpolator = Interpolators.STANDARD
-                addUpdateListener(ValueAnimator.AnimatorUpdateListener {
-                    animation: ValueAnimator ->
-                    rimProgress = animation.animatedValue as Float
-                    invalidate()
-                })
-            }
-            playSequentially(pulseInwards, pulseOutwards)
-        }
+    override fun updateVisOnUpdateCutout(): Boolean {
+        return false // instead, we always update the visibility whenever face scanning starts/ends
     }
 
     override fun updateProtectionBoundingPath() {
@@ -290,17 +177,153 @@
             // Make sure that our measured height encompasses the extra space for the animation
             mTotalBounds.union(mBoundingRect)
             mTotalBounds.union(
-                    rimRect.left.toInt(),
-                    rimRect.top.toInt(),
-                    rimRect.right.toInt(),
-                    rimRect.bottom.toInt())
+                rimRect.left.toInt(),
+                rimRect.top.toInt(),
+                rimRect.right.toInt(),
+                rimRect.bottom.toInt())
             setMeasuredDimension(
-                    resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0),
-                    resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0))
+                resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0),
+                resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0))
         } else {
             setMeasuredDimension(
-                    resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
-                    resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0))
+                resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
+                resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0))
+        }
+    }
+
+    private fun drawFaceScanningRim(canvas: Canvas) {
+        val rimPath = Path(protectionPath)
+        scalePath(rimPath, rimProgress)
+        rimPaint.style = Paint.Style.FILL
+        val rimPaintAlpha = rimPaint.alpha
+        rimPaint.color = ColorUtils.blendARGB(
+            faceScanningAnimColor,
+            Color.WHITE,
+            statusBarStateController.dozeAmount
+        )
+        rimPaint.alpha = rimPaintAlpha
+        canvas.drawPath(rimPath, rimPaint)
+    }
+
+    private fun drawCameraProtection(canvas: Canvas) {
+        val scaledProtectionPath = Path(protectionPath)
+        scalePath(scaledProtectionPath, cameraProtectionProgress)
+        paint.style = Paint.Style.FILL
+        paint.color = cameraProtectionColor
+        canvas.drawPath(scaledProtectionPath, paint)
+    }
+
+    private fun createFaceSuccessRimAnimator(): AnimatorSet {
+        val rimSuccessAnimator = AnimatorSet()
+        rimSuccessAnimator.playTogether(
+            createRimDisappearAnimator(
+                PULSE_RADIUS_SUCCESS,
+                PULSE_SUCCESS_DISAPPEAR_DURATION,
+                Interpolators.STANDARD_DECELERATE
+            ),
+            createSuccessOpacityAnimator(),
+        )
+        return AnimatorSet().apply {
+            playTogether(rimSuccessAnimator, cameraProtectionAnimator)
+        }
+    }
+
+    private fun createFaceNotSuccessRimAnimator(): AnimatorSet {
+        return AnimatorSet().apply {
+            playTogether(
+                createRimDisappearAnimator(
+                    SHOW_CAMERA_PROTECTION_SCALE,
+                    PULSE_ERROR_DISAPPEAR_DURATION,
+                    Interpolators.STANDARD
+                ),
+                cameraProtectionAnimator,
+            )
+        }
+    }
+
+    private fun createRimDisappearAnimator(
+        endValue: Float,
+        animDuration: Long,
+        timeInterpolator: TimeInterpolator
+    ): ValueAnimator {
+        return ValueAnimator.ofFloat(rimProgress, endValue).apply {
+            duration = animDuration
+            interpolator = timeInterpolator
+            addUpdateListener(this@FaceScanningOverlay::updateRimProgress)
+            addListener(object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator) {
+                    rimProgress = HIDDEN_RIM_SCALE
+                    invalidate()
+                }
+            })
+        }
+    }
+
+    private fun createSuccessOpacityAnimator(): ValueAnimator {
+        return ValueAnimator.ofInt(255, 0).apply {
+            duration = PULSE_SUCCESS_DISAPPEAR_DURATION
+            interpolator = Interpolators.LINEAR
+            addUpdateListener(this@FaceScanningOverlay::updateRimAlpha)
+            addListener(object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator) {
+                    rimPaint.alpha = 255
+                    invalidate()
+                }
+            })
+        }
+    }
+
+    private fun createFaceScanningRimAnimator(): AnimatorSet {
+        return AnimatorSet().apply {
+            playSequentially(
+                cameraProtectionAnimator,
+                createRimAppearAnimator(),
+                createPulseAnimator()
+            )
+        }
+    }
+
+    private fun createRimAppearAnimator(): ValueAnimator {
+        return ValueAnimator.ofFloat(
+            SHOW_CAMERA_PROTECTION_SCALE,
+            PULSE_RADIUS_OUT
+        ).apply {
+            duration = PULSE_APPEAR_DURATION
+            interpolator = Interpolators.STANDARD_DECELERATE
+            addUpdateListener(this@FaceScanningOverlay::updateRimProgress)
+        }
+    }
+
+    private fun hide() {
+        visibility = INVISIBLE
+        hideOverlayRunnable?.run()
+        hideOverlayRunnable = null
+        requestLayout()
+    }
+
+    private fun updateRimProgress(animator: ValueAnimator) {
+        rimProgress = animator.animatedValue as Float
+        invalidate()
+    }
+
+    private fun updateCameraProtectionProgress(animator: ValueAnimator) {
+        cameraProtectionProgress = animator.animatedValue as Float
+        invalidate()
+    }
+
+    private fun updateRimAlpha(animator: ValueAnimator) {
+        rimPaint.alpha = animator.animatedValue as Int
+        invalidate()
+    }
+
+    private fun createPulseAnimator(): ValueAnimator {
+        return ValueAnimator.ofFloat(
+                PULSE_RADIUS_OUT, PULSE_RADIUS_IN).apply {
+            duration = HALF_PULSE_DURATION
+            interpolator = Interpolators.STANDARD
+            repeatCount = 11 // Pulse inwards and outwards, reversing direction, 6 times
+            repeatMode = ValueAnimator.REVERSE
+            addUpdateListener(this@FaceScanningOverlay::updateRimProgress)
         }
     }
 
@@ -363,13 +386,24 @@
         private const val CAMERA_PROTECTION_APPEAR_DURATION = 250L
         private const val PULSE_APPEAR_DURATION = 250L // without start delay
 
-        private const val PULSE_DURATION_INWARDS = 500L
-        private const val PULSE_DURATION_OUTWARDS = 500L
+        private const val HALF_PULSE_DURATION = 500L
 
         private const val PULSE_SUCCESS_DISAPPEAR_DURATION = 400L
         private const val CAMERA_PROTECTION_SUCCESS_DISAPPEAR_DURATION = 500L // without start delay
 
         private const val PULSE_ERROR_DISAPPEAR_DURATION = 200L
         private const val CAMERA_PROTECTION_ERROR_DISAPPEAR_DURATION = 300L // without start delay
+
+        private fun scalePath(path: Path, scalingFactor: Float) {
+            val scaleMatrix = Matrix().apply {
+                val boundingRectangle = RectF()
+                path.computeBounds(boundingRectangle, true)
+                setScale(
+                    scalingFactor, scalingFactor,
+                    boundingRectangle.centerX(), boundingRectangle.centerY()
+                )
+            }
+            path.transform(scaleMatrix)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ProtoDumpable.kt b/packages/SystemUI/src/com/android/systemui/ProtoDumpable.kt
new file mode 100644
index 0000000..4c3a7ff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ProtoDumpable.kt
@@ -0,0 +1,7 @@
+package com.android.systemui
+
+import com.android.systemui.dump.nano.SystemUIProtoDump
+
+interface ProtoDumpable : Dumpable {
+    fun dumpProto(systemUIProtoDump: SystemUIProtoDump, args: Array<String>)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index b5f42a1..11d579d 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -456,7 +456,6 @@
                     }
                 }
 
-                boolean needToUpdateProviderViews = false;
                 final String newUniqueId = mDisplayInfo.uniqueId;
                 if (!Objects.equals(newUniqueId, mDisplayUniqueId)) {
                     mDisplayUniqueId = newUniqueId;
@@ -474,37 +473,6 @@
                         setupDecorations();
                         return;
                     }
-
-                    if (mScreenDecorHwcLayer != null) {
-                        updateHwLayerRoundedCornerDrawable();
-                        updateHwLayerRoundedCornerExistAndSize();
-                    }
-                    needToUpdateProviderViews = true;
-                }
-
-                final float newRatio = getPhysicalPixelDisplaySizeRatio();
-                if (mRoundedCornerResDelegate.getPhysicalPixelDisplaySizeRatio() != newRatio) {
-                    mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(newRatio);
-                    if (mScreenDecorHwcLayer != null) {
-                        updateHwLayerRoundedCornerExistAndSize();
-                    }
-                    needToUpdateProviderViews = true;
-                }
-
-                if (needToUpdateProviderViews) {
-                    updateOverlayProviderViews(null);
-                } else {
-                    updateOverlayProviderViews(new Integer[] {
-                            mFaceScanningViewId,
-                            R.id.display_cutout,
-                            R.id.display_cutout_left,
-                            R.id.display_cutout_right,
-                            R.id.display_cutout_bottom,
-                    });
-                }
-
-                if (mScreenDecorHwcLayer != null) {
-                    mScreenDecorHwcLayer.onDisplayChanged(newUniqueId);
                 }
             }
         };
@@ -1070,9 +1038,11 @@
                 && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod))) {
             mRotation = newRotation;
             mDisplayMode = newMod;
+            mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(
+                    getPhysicalPixelDisplaySizeRatio());
             if (mScreenDecorHwcLayer != null) {
                 mScreenDecorHwcLayer.pendingConfigChange = false;
-                mScreenDecorHwcLayer.updateRotation(mRotation);
+                mScreenDecorHwcLayer.updateConfiguration(mDisplayUniqueId);
                 updateHwLayerRoundedCornerExistAndSize();
                 updateHwLayerRoundedCornerDrawable();
             }
@@ -1111,7 +1081,8 @@
                 context.getResources(), context.getDisplay().getUniqueId());
     }
 
-    private void updateOverlayProviderViews(@Nullable Integer[] filterIds) {
+    @VisibleForTesting
+    void updateOverlayProviderViews(@Nullable Integer[] filterIds) {
         if (mOverlays == null) {
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 7bcba3c..50e0399 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -121,6 +121,6 @@
                     DumpHandler.PRIORITY_ARG_CRITICAL};
         }
 
-        mDumpHandler.dump(pw, massagedArgs);
+        mDumpHandler.dump(fd, pw, massagedArgs);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index d43e5d9..c015a21 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -294,6 +294,8 @@
                 }
             });
             mUdfpsController.setAuthControllerUpdateUdfpsLocation(this::updateUdfpsLocation);
+            mUdfpsController.setUdfpsDisplayMode(new UdfpsDisplayMode(mContext, mExecution,
+                    this));
             mUdfpsBounds = mUdfpsProps.get(0).getLocation().getRect();
         }
 
@@ -626,17 +628,6 @@
         mUdfpsController.onAodInterrupt(screenX, screenY, major, minor);
     }
 
-    /**
-     * Cancel a fingerprint scan manually. This will get rid of the white circle on the udfps
-     * sensor area even if the user hasn't explicitly lifted their finger yet.
-     */
-    public void onCancelUdfps() {
-        if (mUdfpsController == null) {
-            return;
-        }
-        mUdfpsController.onCancelUdfps();
-    }
-
     private void sendResultAndCleanUp(@DismissedReason int reason,
             @Nullable byte[] credentialAttestation) {
         if (mReceiver == null) {
@@ -964,8 +955,6 @@
         } else {
             Log.w(TAG, "onBiometricError callback but dialog is gone");
         }
-
-        onCancelUdfps();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
index 0892612..76cd3f4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
@@ -16,12 +16,21 @@
 
 package com.android.systemui.biometrics;
 
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.WindowInsets.Type.ime;
+
 import android.annotation.NonNull;
 import android.content.Context;
+import android.graphics.Insets;
 import android.os.UserHandle;
 import android.text.InputType;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
+import android.view.View;
+import android.view.View.OnApplyWindowInsetsListener;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.ImeAwareEditText;
@@ -31,18 +40,24 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 
+import java.io.PrintWriter;
+
 /**
  * Pin and Password UI
  */
 public class AuthCredentialPasswordView extends AuthCredentialView
-        implements TextView.OnEditorActionListener {
+        implements TextView.OnEditorActionListener, OnApplyWindowInsetsListener, Dumpable {
 
     private static final String TAG = "BiometricPrompt/AuthCredentialPasswordView";
 
     private final InputMethodManager mImm;
     private ImeAwareEditText mPasswordField;
+    private ViewGroup mAuthCredentialHeader;
+    private ViewGroup mAuthCredentialInput;
+    private int mBottomInset = 0;
 
     public AuthCredentialPasswordView(Context context,
             AttributeSet attrs) {
@@ -53,6 +68,9 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+
+        mAuthCredentialHeader = findViewById(R.id.auth_credential_header);
+        mAuthCredentialInput = findViewById(R.id.auth_credential_input);
         mPasswordField = findViewById(R.id.lockPassword);
         mPasswordField.setOnEditorActionListener(this);
         // TODO: De-dupe the logic with AuthContainerView
@@ -66,6 +84,8 @@
             }
             return true;
         });
+
+        setOnApplyWindowInsetsListener(this);
     }
 
     @Override
@@ -127,4 +147,92 @@
             mPasswordField.setText("");
         }
     }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+
+        if (mAuthCredentialInput == null || mAuthCredentialHeader == null || mSubtitleView == null
+                || mDescriptionView == null || mPasswordField == null || mErrorView == null) {
+            return;
+        }
+
+        int inputLeftBound;
+        int inputTopBound;
+        int headerRightBound = right;
+        int headerTopBounds = top;
+        final int subTitleBottom = (mSubtitleView.getVisibility() == GONE) ? mTitleView.getBottom()
+                : mSubtitleView.getBottom();
+        final int descBottom = (mDescriptionView.getVisibility() == GONE) ? subTitleBottom
+                : mDescriptionView.getBottom();
+        if (getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
+            inputTopBound = (bottom - mAuthCredentialInput.getHeight()) / 2;
+            inputLeftBound = (right - left) / 2;
+            headerRightBound = inputLeftBound;
+            headerTopBounds -= Math.min(mIconView.getBottom(), mBottomInset);
+        } else {
+            inputTopBound =
+                    descBottom + (bottom - descBottom - mAuthCredentialInput.getHeight()) / 2;
+            inputLeftBound = (right - left - mAuthCredentialInput.getWidth()) / 2;
+        }
+
+        if (mDescriptionView.getBottom() > mBottomInset) {
+            mAuthCredentialHeader.layout(left, headerTopBounds, headerRightBound, bottom);
+        }
+        mAuthCredentialInput.layout(inputLeftBound, inputTopBound, right, bottom);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        final int newWidth = MeasureSpec.getSize(widthMeasureSpec);
+        final int newHeight = MeasureSpec.getSize(heightMeasureSpec) - mBottomInset;
+
+        setMeasuredDimension(newWidth, newHeight);
+
+        final int halfWidthSpec = MeasureSpec.makeMeasureSpec(getWidth() / 2,
+                MeasureSpec.AT_MOST);
+        final int fullHeightSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.UNSPECIFIED);
+        if (getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
+            measureChildren(halfWidthSpec, fullHeightSpec);
+        } else {
+            measureChildren(widthMeasureSpec, fullHeightSpec);
+        }
+    }
+
+    @NonNull
+    @Override
+    public WindowInsets onApplyWindowInsets(@NonNull View v, WindowInsets insets) {
+
+        final Insets bottomInset = insets.getInsets(ime());
+        if (v instanceof AuthCredentialPasswordView && mBottomInset != bottomInset.bottom) {
+            mBottomInset = bottomInset.bottom;
+            if (mBottomInset > 0
+                    && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
+                mTitleView.setSingleLine(true);
+                mTitleView.setEllipsize(TextUtils.TruncateAt.MARQUEE);
+                mTitleView.setMarqueeRepeatLimit(-1);
+                // select to enable marquee unless a screen reader is enabled
+                mTitleView.setSelected(!mAccessibilityManager.isEnabled()
+                        || !mAccessibilityManager.isTouchExplorationEnabled());
+            } else {
+                mTitleView.setSingleLine(false);
+                mTitleView.setEllipsize(null);
+                // select to enable marquee unless a screen reader is enabled
+                mTitleView.setSelected(false);
+            }
+            requestLayout();
+        }
+        return insets;
+    }
+
+    @Override
+    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println(TAG + "State:");
+        pw.println("  mBottomInset=" + mBottomInset);
+        pw.println("  mAuthCredentialHeader size=(" + mAuthCredentialHeader.getWidth() + ","
+                + mAuthCredentialHeader.getHeight());
+        pw.println("  mAuthCredentialInput size=(" + mAuthCredentialInput.getWidth() + ","
+                + mAuthCredentialInput.getHeight());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
index 11498db..f9e44a0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
@@ -93,7 +93,9 @@
     @Override
     protected void onErrorTimeoutFinish() {
         super.onErrorTimeoutFinish();
-        mLockPatternView.setEnabled(true);
+        // select to enable marquee unless a screen reader is enabled
+        mLockPatternView.setEnabled(!mAccessibilityManager.isEnabled()
+                || !mAccessibilityManager.isTouchExplorationEnabled());
     }
 
     public AuthCredentialPatternView(Context context, AttributeSet attrs) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
index 4fa835e..5958e6a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
@@ -30,7 +30,6 @@
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.graphics.drawable.Drawable;
-import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.PromptInfo;
 import android.os.AsyncTask;
 import android.os.CountDownTimer;
@@ -77,7 +76,7 @@
     protected final Handler mHandler;
     protected final LockPatternUtils mLockPatternUtils;
 
-    private final AccessibilityManager mAccessibilityManager;
+    protected final AccessibilityManager mAccessibilityManager;
     private final UserManager mUserManager;
     private final DevicePolicyManager mDevicePolicyManager;
 
@@ -86,10 +85,10 @@
     private boolean mShouldAnimatePanel;
     private boolean mShouldAnimateContents;
 
-    private TextView mTitleView;
-    private TextView mSubtitleView;
-    private TextView mDescriptionView;
-    private ImageView mIconView;
+    protected TextView mTitleView;
+    protected TextView mSubtitleView;
+    protected TextView mDescriptionView;
+    protected ImageView mIconView;
     protected TextView mErrorView;
 
     protected @Utils.CredentialType int mCredentialType;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index a7648bf..b49d452 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -123,7 +123,6 @@
     @NonNull private final PowerManager mPowerManager;
     @NonNull private final AccessibilityManager mAccessibilityManager;
     @NonNull private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
-    @Nullable private final UdfpsDisplayModeProvider mUdfpsDisplayMode;
     @NonNull private final ConfigurationController mConfigurationController;
     @NonNull private final SystemClock mSystemClock;
     @NonNull private final UnlockedScreenOffAnimationController
@@ -139,6 +138,7 @@
     // TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this.
     @Nullable private Runnable mAuthControllerUpdateUdfpsLocation;
     @Nullable private final AlternateUdfpsTouchProvider mAlternateTouchProvider;
+    @Nullable private UdfpsDisplayMode mUdfpsDisplayMode;
 
     // Tracks the velocity of a touch to help filter out the touches that move too fast.
     @Nullable private VelocityTracker mVelocityTracker;
@@ -319,6 +319,10 @@
         mAuthControllerUpdateUdfpsLocation = r;
     }
 
+    public void setUdfpsDisplayMode(UdfpsDisplayMode udfpsDisplayMode) {
+        mUdfpsDisplayMode = udfpsDisplayMode;
+    }
+
     /**
      * Calculate the pointer speed given a velocity tracker and the pointer id.
      * This assumes that the velocity tracker has already been passed all relevant motion events.
@@ -594,7 +598,6 @@
             @NonNull VibratorHelper vibrator,
             @NonNull UdfpsHapticsSimulator udfpsHapticsSimulator,
             @NonNull UdfpsShell udfpsShell,
-            @NonNull Optional<UdfpsDisplayModeProvider> udfpsDisplayMode,
             @NonNull KeyguardStateController keyguardStateController,
             @NonNull DisplayManager displayManager,
             @Main Handler mainHandler,
@@ -626,7 +629,6 @@
         mPowerManager = powerManager;
         mAccessibilityManager = accessibilityManager;
         mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
-        mUdfpsDisplayMode = udfpsDisplayMode.orElse(null);
         screenLifecycle.addObserver(mScreenObserver);
         mScreenOn = screenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_ON;
         mConfigurationController = configurationController;
@@ -786,7 +788,7 @@
             // ACTION_UP/ACTION_CANCEL,  we need to be careful about not letting the screen
             // accidentally remain in high brightness mode. As a mitigation, queue a call to
             // cancel the fingerprint scan.
-            mCancelAodTimeoutAction = mFgExecutor.executeDelayed(this::onCancelUdfps,
+            mCancelAodTimeoutAction = mFgExecutor.executeDelayed(this::cancelAodInterrupt,
                     AOD_INTERRUPT_TIMEOUT_MILLIS);
             // using a hard-coded value for major and minor until it is available from the sensor
             onFingerDown(requestId, screenX, screenY, minor, major);
@@ -813,26 +815,22 @@
     }
 
     /**
-     * Cancel UDFPS affordances - ability to hide the UDFPS overlay before the user explicitly
-     * lifts their finger. Generally, this should be called on errors in the authentication flow.
-     *
-     * The sensor that triggers an AOD fingerprint interrupt (see onAodInterrupt) doesn't give
-     * ACTION_UP/ACTION_CANCEL events, so and AOD interrupt scan needs to be cancelled manually.
+     * The sensor that triggers {@link #onAodInterrupt} doesn't emit ACTION_UP or ACTION_CANCEL
+     * events, which means the fingerprint gesture created by the AOD interrupt needs to be
+     * cancelled manually.
      * This should be called when authentication either succeeds or fails. Failing to cancel the
      * scan will leave the display in the UDFPS mode until the user lifts their finger. On optical
      * sensors, this can result in illumination persisting for longer than necessary.
      */
-    void onCancelUdfps() {
+    @VisibleForTesting
+    void cancelAodInterrupt() {
         if (!mIsAodInterruptActive) {
             return;
         }
         if (mOverlay != null && mOverlay.getOverlayView() != null) {
             onFingerUp(mOverlay.getRequestId(), mOverlay.getOverlayView());
         }
-        if (mCancelAodTimeoutAction != null) {
-            mCancelAodTimeoutAction.run();
-            mCancelAodTimeoutAction = null;
-        }
+        mCancelAodTimeoutAction = null;
         mIsAodInterruptActive = false;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 66a521c..7d01096 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -21,13 +21,18 @@
 import android.content.Context
 import android.graphics.PixelFormat
 import android.graphics.Rect
-import android.hardware.biometrics.BiometricOverlayConstants
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
 import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
 import android.hardware.fingerprint.FingerprintManager
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
+import android.os.Build
 import android.os.RemoteException
+import android.provider.Settings
 import android.util.Log
 import android.util.RotationUtils
 import android.view.LayoutInflater
@@ -38,6 +43,7 @@
 import android.view.accessibility.AccessibilityManager
 import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener
 import androidx.annotation.LayoutRes
+import androidx.annotation.VisibleForTesting
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
@@ -54,13 +60,16 @@
 
 private const val TAG = "UdfpsControllerOverlay"
 
+@VisibleForTesting
+const val SETTING_REMOVE_ENROLLMENT_UI = "udfps_overlay_remove_enrollment_ui"
+
 /**
  * Keeps track of the overlay state and UI resources associated with a single FingerprintService
  * request. This state can persist across configuration changes via the [show] and [hide]
  * methods.
  */
 @UiThread
-class UdfpsControllerOverlay(
+class UdfpsControllerOverlay @JvmOverloads constructor(
     private val context: Context,
     fingerprintManager: FingerprintManager,
     private val inflater: LayoutInflater,
@@ -82,7 +91,8 @@
     @ShowReason val requestReason: Int,
     private val controllerCallback: IUdfpsOverlayControllerCallback,
     private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
-    private val activityLaunchAnimator: ActivityLaunchAnimator
+    private val activityLaunchAnimator: ActivityLaunchAnimator,
+    private val isDebuggable: Boolean = Build.IS_DEBUGGABLE
 ) {
     /** The view, when [isShowing], or null. */
     var overlayView: UdfpsView? = null
@@ -102,18 +112,19 @@
         gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT
         layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
         flags = (Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS or
-          WindowManager.LayoutParams.FLAG_SPLIT_TOUCH)
+                WindowManager.LayoutParams.FLAG_SPLIT_TOUCH)
         privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
         // Avoid announcing window title.
         accessibilityTitle = " "
     }
 
     /** A helper if the [requestReason] was due to enrollment. */
-    val enrollHelper: UdfpsEnrollHelper? = if (requestReason.isEnrollmentReason()) {
-        UdfpsEnrollHelper(context, fingerprintManager, requestReason)
-    } else {
-        null
-    }
+    val enrollHelper: UdfpsEnrollHelper? =
+        if (requestReason.isEnrollmentReason() && !shouldRemoveEnrollmentUi()) {
+            UdfpsEnrollHelper(context, fingerprintManager, requestReason)
+        } else {
+            null
+        }
 
     /** If the overlay is currently showing. */
     val isShowing: Boolean
@@ -129,6 +140,17 @@
 
     private var touchExplorationEnabled = false
 
+    private fun shouldRemoveEnrollmentUi(): Boolean {
+        if (isDebuggable) {
+            return Settings.Global.getInt(
+                context.contentResolver,
+                SETTING_REMOVE_ENROLLMENT_UI,
+                0 /* def */
+            ) != 0
+        }
+        return false
+    }
+
     /** Show the overlay or return false and do nothing if it is already showing. */
     @SuppressLint("ClickableViewAccessibility")
     fun show(controller: UdfpsController, params: UdfpsOverlayParams): Boolean {
@@ -183,7 +205,18 @@
         view: UdfpsView,
         controller: UdfpsController
     ): UdfpsAnimationViewController<*>? {
-        return when (requestReason) {
+        val isEnrollment = when (requestReason) {
+            REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING -> true
+            else -> false
+        }
+
+        val filteredRequestReason = if (isEnrollment && shouldRemoveEnrollmentUi()) {
+            REASON_AUTH_OTHER
+        } else {
+            requestReason
+        }
+
+        return when (filteredRequestReason) {
             REASON_ENROLL_FIND_SENSOR,
             REASON_ENROLL_ENROLLING -> {
                 UdfpsEnrollViewController(
@@ -198,7 +231,7 @@
                     overlayParams.scaleFactor
                 )
             }
-            BiometricOverlayConstants.REASON_AUTH_KEYGUARD -> {
+            REASON_AUTH_KEYGUARD -> {
                 UdfpsKeyguardViewController(
                     view.addUdfpsView(R.layout.udfps_keyguard_view),
                     statusBarStateController,
@@ -216,7 +249,7 @@
                     activityLaunchAnimator
                 )
             }
-            BiometricOverlayConstants.REASON_AUTH_BP -> {
+            REASON_AUTH_BP -> {
                 // note: empty controller, currently shows no visual affordance
                 UdfpsBpViewController(
                     view.addUdfpsView(R.layout.udfps_bp_view),
@@ -226,8 +259,8 @@
                     dumpManager
                 )
             }
-            BiometricOverlayConstants.REASON_AUTH_OTHER,
-            BiometricOverlayConstants.REASON_AUTH_SETTINGS -> {
+            REASON_AUTH_OTHER,
+            REASON_AUTH_SETTINGS -> {
                 UdfpsFpmOtherViewController(
                     view.addUdfpsView(R.layout.udfps_fpm_other_view),
                     statusBarStateController,
@@ -440,4 +473,4 @@
 private fun Int.isImportantForAccessibility() =
     this == REASON_ENROLL_FIND_SENSOR ||
             this == REASON_ENROLL_ENROLLING ||
-            this == BiometricOverlayConstants.REASON_AUTH_BP
+            this == REASON_AUTH_BP
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt
new file mode 100644
index 0000000..e9de7cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt
@@ -0,0 +1,88 @@
+package com.android.systemui.biometrics
+
+import android.content.Context
+import android.os.RemoteException
+import android.os.Trace
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.util.concurrency.Execution
+import javax.inject.Inject
+
+private const val TAG = "UdfpsDisplayMode"
+
+/**
+ * UdfpsDisplayMode that encapsulates pixel-specific code, such as enabling the high-brightness mode
+ * (HBM) in a display-specific way and freezing the display's refresh rate.
+ */
+@SysUISingleton
+class UdfpsDisplayMode
+@Inject
+constructor(
+    private val context: Context,
+    private val execution: Execution,
+    private val authController: AuthController
+) : UdfpsDisplayModeProvider {
+
+    // The request is reset to null after it's processed.
+    private var currentRequest: Request? = null
+
+    override fun enable(onEnabled: Runnable?) {
+        execution.isMainThread()
+        Log.v(TAG, "enable")
+
+        if (currentRequest != null) {
+            Log.e(TAG, "enable | already requested")
+            return
+        }
+        if (authController.udfpsHbmListener == null) {
+            Log.e(TAG, "enable | mDisplayManagerCallback is null")
+            return
+        }
+
+        Trace.beginSection("UdfpsDisplayMode.enable")
+
+        // Track this request in one object.
+        val request = Request(context.displayId)
+        currentRequest = request
+
+        try {
+            // This method is a misnomer. It has nothing to do with HBM, its purpose is to set
+            // the appropriate display refresh rate.
+            authController.udfpsHbmListener!!.onHbmEnabled(request.displayId)
+            Log.v(TAG, "enable | requested optimal refresh rate for UDFPS")
+        } catch (e: RemoteException) {
+            Log.e(TAG, "enable", e)
+        }
+
+        onEnabled?.run() ?: Log.w(TAG, "enable | onEnabled is null")
+        Trace.endSection()
+    }
+
+    override fun disable(onDisabled: Runnable?) {
+        execution.isMainThread()
+        Log.v(TAG, "disable")
+
+        val request = currentRequest
+        if (request == null) {
+            Log.w(TAG, "disable | already disabled")
+            return
+        }
+
+        Trace.beginSection("UdfpsDisplayMode.disable")
+
+        try {
+            // Allow DisplayManager to unset the UDFPS refresh rate.
+            authController.udfpsHbmListener!!.onHbmDisabled(request.displayId)
+            Log.v(TAG, "disable | removed the UDFPS refresh rate request")
+        } catch (e: RemoteException) {
+            Log.e(TAG, "disable", e)
+        }
+
+        currentRequest = null
+        onDisabled?.run() ?: Log.w(TAG, "disable | onDisabled is null")
+        Trace.endSection()
+    }
+}
+
+/** Tracks a request to enable the UDFPS mode. */
+private data class Request(val displayId: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
index 96af42b..d99625a 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
@@ -17,9 +17,9 @@
 package com.android.systemui.bluetooth
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.BluetoothLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
 import javax.inject.Inject
 
 /** Helper class for logging bluetooth events. */
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java
index 9b7d498..8e062bd 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java
@@ -17,15 +17,11 @@
 package com.android.systemui.bluetooth;
 
 import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
 import android.os.Bundle;
-import android.text.TextUtils;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.Window;
-import android.view.WindowManager;
 import android.widget.Button;
 import android.widget.TextView;
 
@@ -33,7 +29,7 @@
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
-import com.android.systemui.media.MediaDataUtils;
+import com.android.systemui.media.controls.util.MediaDataUtils;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
index 5b3a982..d27708f 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
@@ -20,11 +20,11 @@
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogMessage
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogMessage
 import com.android.systemui.log.dagger.BroadcastDispatcherLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index d53e56f..500f280 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -18,6 +18,7 @@
 
 import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
 import static com.android.systemui.classifier.Classifier.GENERIC;
+import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
 import static com.android.systemui.classifier.FalsingManagerProxy.FALSING_SUCCESS;
 import static com.android.systemui.classifier.FalsingModule.BRIGHT_LINE_GESTURE_CLASSIFERS;
 
@@ -220,6 +221,11 @@
             return r;
         }).collect(Collectors.toList());
 
+        // check for false tap if it is a seekbar interaction
+        if (interactionType == MEDIA_SEEKBAR) {
+            localResult[0] &= isFalseTap(LOW_PENALTY);
+        }
+
         logDebug("False Gesture (type: " + interactionType + "): " + localResult[0]);
 
         return localResult[0];
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
index 3871248..858bac3 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
@@ -44,9 +44,6 @@
     void onQsDown();
 
     /** */
-    void setQsExpanded(boolean expanded);
-
-    /** */
     boolean shouldEnforceBouncer();
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
index 28aac05..0b7d6ab 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
@@ -49,10 +49,6 @@
     }
 
     @Override
-    public void setQsExpanded(boolean expanded) {
-    }
-
-    @Override
     public boolean shouldEnforceBouncer() {
         return false;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index f5f9655..da3d293 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -23,6 +23,8 @@
 import android.util.Log;
 import android.view.MotionEvent;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.dagger.SysUISingleton;
@@ -30,6 +32,7 @@
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -133,6 +136,7 @@
             ProximitySensor proximitySensor,
             StatusBarStateController statusBarStateController,
             KeyguardStateController keyguardStateController,
+            ShadeExpansionStateManager shadeExpansionStateManager,
             BatteryController batteryController,
             DockManager dockManager,
             @Main DelayableExecutor mainExecutor,
@@ -157,6 +161,8 @@
 
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
 
+        shadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
+
         mBatteryController.addCallback(mBatteryListener);
         mDockManager.addListener(mDockEventListener);
     }
@@ -193,8 +199,8 @@
     public void onQsDown() {
     }
 
-    @Override
-    public void setQsExpanded(boolean expanded) {
+    @VisibleForTesting
+    void onQsExpansionChanged(Boolean expanded) {
         if (expanded) {
             unregisterSensors();
         } else if (mSessionStarted) {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 05e3f1c..82e5704 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -31,9 +31,12 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.util.DeviceConfigProxy;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 /**
  * ClipboardListener brings up a clipboard overlay when something is copied to the clipboard.
@@ -51,20 +54,30 @@
 
     private final Context mContext;
     private final DeviceConfigProxy mDeviceConfig;
-    private final ClipboardOverlayControllerFactory mOverlayFactory;
+    private final Provider<ClipboardOverlayController> mOverlayProvider;
+    private final ClipboardOverlayControllerLegacyFactory mOverlayFactory;
     private final ClipboardManager mClipboardManager;
     private final UiEventLogger mUiEventLogger;
-    private ClipboardOverlayController mClipboardOverlayController;
+    private final FeatureFlags mFeatureFlags;
+    private boolean mUsingNewOverlay;
+    private ClipboardOverlay mClipboardOverlay;
 
     @Inject
     public ClipboardListener(Context context, DeviceConfigProxy deviceConfigProxy,
-            ClipboardOverlayControllerFactory overlayFactory, ClipboardManager clipboardManager,
-            UiEventLogger uiEventLogger) {
+            Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
+            ClipboardOverlayControllerLegacyFactory overlayFactory,
+            ClipboardManager clipboardManager,
+            UiEventLogger uiEventLogger,
+            FeatureFlags featureFlags) {
         mContext = context;
         mDeviceConfig = deviceConfigProxy;
+        mOverlayProvider = clipboardOverlayControllerProvider;
         mOverlayFactory = overlayFactory;
         mClipboardManager = clipboardManager;
         mUiEventLogger = uiEventLogger;
+        mFeatureFlags = featureFlags;
+
+        mUsingNewOverlay = mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR);
     }
 
     @Override
@@ -89,16 +102,22 @@
             return;
         }
 
-        if (mClipboardOverlayController == null) {
-            mClipboardOverlayController = mOverlayFactory.create(mContext);
+        boolean enabled = mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR);
+        if (mClipboardOverlay == null || enabled != mUsingNewOverlay) {
+            mUsingNewOverlay = enabled;
+            if (enabled) {
+                mClipboardOverlay = mOverlayProvider.get();
+            } else {
+                mClipboardOverlay = mOverlayFactory.create(mContext);
+            }
             mUiEventLogger.log(CLIPBOARD_OVERLAY_ENTERED, 0, clipSource);
         } else {
             mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource);
         }
-        mClipboardOverlayController.setClipData(clipData, clipSource);
-        mClipboardOverlayController.setOnSessionCompleteListener(() -> {
+        mClipboardOverlay.setClipData(clipData, clipSource);
+        mClipboardOverlay.setOnSessionCompleteListener(() -> {
             // Session is complete, free memory until it's needed again.
-            mClipboardOverlayController = null;
+            mClipboardOverlay = null;
         });
     }
 
@@ -120,4 +139,10 @@
     private static boolean isEmulator() {
         return SystemProperties.getBoolean("ro.boot.qemu", false);
     }
+
+    interface ClipboardOverlay {
+        void setClipData(ClipData clipData, String clipSource);
+
+        void setOnSessionCompleteListener(Runnable runnable);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 7e499eb..9f338d1 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -17,7 +17,6 @@
 package com.android.systemui.clipboardoverlay;
 
 import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
 
@@ -37,11 +36,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.annotation.MainThread;
-import android.app.ICompatCameraControlCallback;
 import android.app.RemoteAction;
 import android.content.BroadcastReceiver;
 import android.content.ClipData;
@@ -52,14 +46,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.Insets;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.drawable.Icon;
 import android.hardware.display.DisplayManager;
 import android.hardware.input.InputManager;
 import android.net.Uri;
@@ -67,57 +54,37 @@
 import android.os.Looper;
 import android.provider.DeviceConfig;
 import android.text.TextUtils;
-import android.util.DisplayMetrics;
 import android.util.Log;
-import android.util.MathUtils;
 import android.util.Size;
-import android.util.TypedValue;
 import android.view.Display;
-import android.view.DisplayCutout;
-import android.view.Gravity;
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
 import android.view.InputMonitor;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewRootImpl;
-import android.view.ViewTreeObserver;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.LinearInterpolator;
-import android.view.animation.PathInterpolator;
 import android.view.textclassifier.TextClassification;
 import android.view.textclassifier.TextClassificationManager;
 import android.view.textclassifier.TextClassifier;
 import android.view.textclassifier.TextLinks;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
 
 import androidx.annotation.NonNull;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
 
 import com.android.internal.logging.UiEventLogger;
-import com.android.internal.policy.PhoneWindow;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.screenshot.DraggableConstraintLayout;
-import com.android.systemui.screenshot.FloatingWindowUtil;
-import com.android.systemui.screenshot.OverlayActionChip;
+import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext;
 import com.android.systemui.screenshot.TimeoutHandler;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Optional;
+
+import javax.inject.Inject;
 
 /**
  * Controls state and UI for the overlay that appears when something is added to the clipboard
  */
-public class ClipboardOverlayController {
+public class ClipboardOverlayController implements ClipboardListener.ClipboardOverlay {
     private static final String TAG = "ClipboardOverlayCtrlr";
 
     /** Constants for screenshot/copy deconflicting */
@@ -126,36 +93,22 @@
     public static final String COPY_OVERLAY_ACTION = "com.android.systemui.COPY";
 
     private static final int CLIPBOARD_DEFAULT_TIMEOUT_MILLIS = 6000;
-    private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
-    private static final int FONT_SEARCH_STEP_PX = 4;
 
     private final Context mContext;
     private final ClipboardLogger mClipboardLogger;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final DisplayManager mDisplayManager;
-    private final DisplayMetrics mDisplayMetrics;
-    private final WindowManager mWindowManager;
-    private final WindowManager.LayoutParams mWindowLayoutParams;
-    private final PhoneWindow mWindow;
+    private final ClipboardOverlayWindow mWindow;
     private final TimeoutHandler mTimeoutHandler;
-    private final AccessibilityManager mAccessibilityManager;
     private final TextClassifier mTextClassifier;
 
-    private final DraggableConstraintLayout mView;
-    private final View mClipboardPreview;
-    private final ImageView mImagePreview;
-    private final TextView mTextPreview;
-    private final TextView mHiddenPreview;
-    private final View mPreviewBorder;
-    private final OverlayActionChip mEditChip;
-    private final OverlayActionChip mShareChip;
-    private final OverlayActionChip mRemoteCopyChip;
-    private final View mActionContainerBackground;
-    private final View mDismissButton;
-    private final LinearLayout mActionContainer;
-    private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>();
+    private final ClipboardOverlayView mView;
 
     private Runnable mOnSessionCompleteListener;
+    private Runnable mOnRemoteCopyTapped;
+    private Runnable mOnShareTapped;
+    private Runnable mOnEditTapped;
+    private Runnable mOnPreviewTapped;
 
     private InputMonitor mInputMonitor;
     private InputEventReceiver mInputEventReceiver;
@@ -163,14 +116,66 @@
     private BroadcastReceiver mCloseDialogsReceiver;
     private BroadcastReceiver mScreenshotReceiver;
 
-    private boolean mBlockAttach = false;
     private Animator mExitAnimator;
     private Animator mEnterAnimator;
-    private final int mOrientation;
-    private boolean mKeyboardVisible;
 
+    private final ClipboardOverlayView.ClipboardOverlayCallbacks mClipboardCallbacks =
+            new ClipboardOverlayView.ClipboardOverlayCallbacks() {
+                @Override
+                public void onInteraction() {
+                    mTimeoutHandler.resetTimeout();
+                }
 
-    public ClipboardOverlayController(Context context,
+                @Override
+                public void onSwipeDismissInitiated(Animator animator) {
+                    mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
+                    mExitAnimator = animator;
+                }
+
+                @Override
+                public void onDismissComplete() {
+                    hideImmediate();
+                }
+
+                @Override
+                public void onPreviewTapped() {
+                    if (mOnPreviewTapped != null) {
+                        mOnPreviewTapped.run();
+                    }
+                }
+
+                @Override
+                public void onShareButtonTapped() {
+                    if (mOnShareTapped != null) {
+                        mOnShareTapped.run();
+                    }
+                }
+
+                @Override
+                public void onEditButtonTapped() {
+                    if (mOnEditTapped != null) {
+                        mOnEditTapped.run();
+                    }
+                }
+
+                @Override
+                public void onRemoteCopyButtonTapped() {
+                    if (mOnRemoteCopyTapped != null) {
+                        mOnRemoteCopyTapped.run();
+                    }
+                }
+
+                @Override
+                public void onDismissButtonTapped() {
+                    mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+                    animateOut();
+                }
+            };
+
+    @Inject
+    public ClipboardOverlayController(@OverlayWindowContext Context context,
+            ClipboardOverlayView clipboardOverlayView,
+            ClipboardOverlayWindow clipboardOverlayWindow,
             BroadcastDispatcher broadcastDispatcher,
             BroadcastSender broadcastSender,
             TimeoutHandler timeoutHandler, UiEventLogger uiEventLogger) {
@@ -181,121 +186,26 @@
 
         mClipboardLogger = new ClipboardLogger(uiEventLogger);
 
-        mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+        mView = clipboardOverlayView;
+        mWindow = clipboardOverlayWindow;
+        mWindow.init(mView::setInsets, () -> {
+            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+            hideImmediate();
+        });
+
         mTextClassifier = requireNonNull(context.getSystemService(TextClassificationManager.class))
                 .getTextClassifier();
 
-        mWindowManager = mContext.getSystemService(WindowManager.class);
-
-        mDisplayMetrics = new DisplayMetrics();
-        mContext.getDisplay().getRealMetrics(mDisplayMetrics);
-
         mTimeoutHandler = timeoutHandler;
         mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
 
-        // Setup the window that we are going to use
-        mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
-        mWindowLayoutParams.setTitle("ClipboardOverlay");
+        mView.setCallbacks(mClipboardCallbacks);
 
-        mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
-        mWindow.setWindowManager(mWindowManager, null, null);
 
-        setWindowFocusable(false);
-
-        mView = (DraggableConstraintLayout)
-                LayoutInflater.from(mContext).inflate(R.layout.clipboard_overlay, null);
-        mActionContainerBackground =
-                requireNonNull(mView.findViewById(R.id.actions_container_background));
-        mActionContainer = requireNonNull(mView.findViewById(R.id.actions));
-        mClipboardPreview = requireNonNull(mView.findViewById(R.id.clipboard_preview));
-        mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview));
-        mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
-        mHiddenPreview = requireNonNull(mView.findViewById(R.id.hidden_preview));
-        mPreviewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
-        mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
-        mShareChip = requireNonNull(mView.findViewById(R.id.share_chip));
-        mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
-        mEditChip.setAlpha(1);
-        mShareChip.setAlpha(1);
-        mRemoteCopyChip.setAlpha(1);
-        mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button));
-
-        mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share));
-        mView.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() {
-            @Override
-            public void onInteraction() {
-                mTimeoutHandler.resetTimeout();
-            }
-
-            @Override
-            public void onSwipeDismissInitiated(Animator animator) {
-                mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
-                mExitAnimator = animator;
-            }
-
-            @Override
-            public void onDismissComplete() {
-                hideImmediate();
-            }
-        });
-
-        mTextPreview.getViewTreeObserver().addOnPreDrawListener(() -> {
-            int availableHeight = mTextPreview.getHeight()
-                    - (mTextPreview.getPaddingTop() + mTextPreview.getPaddingBottom());
-            mTextPreview.setMaxLines(availableHeight / mTextPreview.getLineHeight());
-            return true;
-        });
-
-        mDismissButton.setOnClickListener(view -> {
-            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
-            animateOut();
-        });
-
-        mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
-        mRemoteCopyChip.setIcon(
-                Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
-        mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
-        mOrientation = mContext.getResources().getConfiguration().orientation;
-
-        attachWindow();
-        withWindowAttached(() -> {
+        mWindow.withWindowAttached(() -> {
             mWindow.setContentView(mView);
-            WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
-            mKeyboardVisible = insets.isVisible(WindowInsets.Type.ime());
-            updateInsets(insets);
-            mWindow.peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener(
-                    new ViewTreeObserver.OnGlobalLayoutListener() {
-                        @Override
-                        public void onGlobalLayout() {
-                            WindowInsets insets =
-                                    mWindowManager.getCurrentWindowMetrics().getWindowInsets();
-                            boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime());
-                            if (keyboardVisible != mKeyboardVisible) {
-                                mKeyboardVisible = keyboardVisible;
-                                updateInsets(insets);
-                            }
-                        }
-                    });
-            mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
-                    new ViewRootImpl.ActivityConfigCallback() {
-                        @Override
-                        public void onConfigurationChanged(Configuration overrideConfig,
-                                int newDisplayId) {
-                            if (mContext.getResources().getConfiguration().orientation
-                                    != mOrientation) {
-                                mClipboardLogger.logSessionComplete(
-                                        CLIPBOARD_OVERLAY_DISMISSED_OTHER);
-                                hideImmediate();
-                            }
-                        }
-
-                        @Override
-                        public void requestCompatCameraControl(
-                                boolean showControl, boolean transformationApplied,
-                                ICompatCameraControlCallback callback) {
-                            Log.w(TAG, "unexpected requestCompatCameraControl call");
-                        }
-                    });
+            mView.setInsets(mWindow.getWindowInsets(),
+                    mContext.getResources().getConfiguration().orientation);
         });
 
         mTimeoutHandler.setOnTimeoutRunnable(() -> {
@@ -336,21 +246,19 @@
         broadcastSender.sendBroadcast(copyIntent, SELF_PERMISSION);
     }
 
-    void setClipData(ClipData clipData, String clipSource) {
+    @Override // ClipboardListener.ClipboardOverlay
+    public void setClipData(ClipData clipData, String clipSource) {
         if (mExitAnimator != null && mExitAnimator.isRunning()) {
             mExitAnimator.cancel();
         }
         reset();
-        String accessibilityAnnouncement;
+        String accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
 
         boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null
                 && clipData.getDescription().getExtras()
                 .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
         if (clipData == null || clipData.getItemCount() == 0) {
-            showTextPreview(
-                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
-                    mTextPreview);
-            accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
+            mView.showDefaultTextPreview();
         } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
             ClipData.Item item = clipData.getItemAt(0);
             if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
@@ -360,53 +268,47 @@
                 }
             }
             if (isSensitive) {
-                showEditableText(
-                        mContext.getResources().getString(R.string.clipboard_asterisks), true);
+                showEditableText(mContext.getString(R.string.clipboard_asterisks), true);
             } else {
                 showEditableText(item.getText(), false);
             }
-            showShareChip(clipData);
+            mOnShareTapped = () -> shareContent(clipData);
+            mView.showShareChip();
             accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied);
         } else if (clipData.getItemAt(0).getUri() != null) {
             if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) {
-                showShareChip(clipData);
+                mOnShareTapped = () -> shareContent(clipData);
+                mView.showShareChip();
                 accessibilityAnnouncement = mContext.getString(R.string.clipboard_image_copied);
-            } else {
-                accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
             }
         } else {
-            showTextPreview(
-                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
-                    mTextPreview);
-            accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
+            mView.showDefaultTextPreview();
         }
+        maybeShowRemoteCopy(clipData);
+        animateIn();
+        mView.announceForAccessibility(accessibilityAnnouncement);
+        mTimeoutHandler.resetTimeout();
+    }
+
+    private void maybeShowRemoteCopy(ClipData clipData) {
         Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext);
         // Only show remote copy if it's available.
         PackageManager packageManager = mContext.getPackageManager();
         if (packageManager.resolveActivity(
                 remoteCopyIntent, PackageManager.ResolveInfoFlags.of(0)) != null) {
-            mRemoteCopyChip.setContentDescription(
-                    mContext.getString(R.string.clipboard_send_nearby_description));
-            mRemoteCopyChip.setVisibility(View.VISIBLE);
-            mRemoteCopyChip.setOnClickListener((v) -> {
+            mView.setRemoteCopyVisibility(true);
+            mOnRemoteCopyTapped = () -> {
                 mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED);
                 mContext.startActivity(remoteCopyIntent);
                 animateOut();
-            });
-            mActionContainerBackground.setVisibility(View.VISIBLE);
+            };
         } else {
-            mRemoteCopyChip.setVisibility(View.GONE);
+            mView.setRemoteCopyVisibility(false);
         }
-        withWindowAttached(() -> {
-            if (mEnterAnimator == null || !mEnterAnimator.isRunning()) {
-                mView.post(this::animateIn);
-            }
-            mView.announceForAccessibility(accessibilityAnnouncement);
-        });
-        mTimeoutHandler.resetTimeout();
     }
 
-    void setOnSessionCompleteListener(Runnable runnable) {
+    @Override // ClipboardListener.ClipboardOverlay
+    public void setOnSessionCompleteListener(Runnable runnable) {
         mOnSessionCompleteListener = runnable;
     }
 
@@ -418,72 +320,29 @@
             actions.addAll(classification.getActions());
         }
         mView.post(() -> {
-            resetActionChips();
-            if (actions.size() > 0) {
-                mActionContainerBackground.setVisibility(View.VISIBLE);
-                for (RemoteAction action : actions) {
-                    Intent targetIntent = action.getActionIntent().getIntent();
-                    ComponentName component = targetIntent.getComponent();
-                    if (component != null && !TextUtils.equals(source,
-                            component.getPackageName())) {
-                        OverlayActionChip chip = constructActionChip(action);
-                        mActionContainer.addView(chip);
-                        mActionChips.add(chip);
-                        break; // only show at most one action chip
-                    }
-                }
-            }
+            Optional<RemoteAction> action = actions.stream().filter(remoteAction -> {
+                ComponentName component = remoteAction.getActionIntent().getIntent().getComponent();
+                return component != null && !TextUtils.equals(source, component.getPackageName());
+            }).findFirst();
+            mView.resetActionChips();
+            action.ifPresent(remoteAction -> mView.setActionChip(remoteAction, () -> {
+                mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
+                animateOut();
+            }));
         });
     }
 
-    private void showShareChip(ClipData clip) {
-        mShareChip.setVisibility(View.VISIBLE);
-        mActionContainerBackground.setVisibility(View.VISIBLE);
-        mShareChip.setOnClickListener((v) -> shareContent(clip));
-    }
-
-    private OverlayActionChip constructActionChip(RemoteAction action) {
-        OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate(
-                R.layout.overlay_action_chip, mActionContainer, false);
-        chip.setText(action.getTitle());
-        chip.setContentDescription(action.getTitle());
-        chip.setIcon(action.getIcon(), false);
-        chip.setPendingIntent(action.getActionIntent(), () -> {
-            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
-            animateOut();
-        });
-        chip.setAlpha(1);
-        return chip;
-    }
-
     private void monitorOutsideTouches() {
         InputManager inputManager = mContext.getSystemService(InputManager.class);
         mInputMonitor = inputManager.monitorGestureInput("clipboard overlay", 0);
-        mInputEventReceiver = new InputEventReceiver(mInputMonitor.getInputChannel(),
-                Looper.getMainLooper()) {
+        mInputEventReceiver = new InputEventReceiver(
+                mInputMonitor.getInputChannel(), Looper.getMainLooper()) {
             @Override
             public void onInputEvent(InputEvent event) {
                 if (event instanceof MotionEvent) {
                     MotionEvent motionEvent = (MotionEvent) event;
                     if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
-                        Region touchRegion = new Region();
-
-                        final Rect tmpRect = new Rect();
-                        mPreviewBorder.getBoundsOnScreen(tmpRect);
-                        tmpRect.inset(
-                                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
-                                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
-                                        -SWIPE_PADDING_DP));
-                        touchRegion.op(tmpRect, Region.Op.UNION);
-                        mActionContainerBackground.getBoundsOnScreen(tmpRect);
-                        tmpRect.inset(
-                                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
-                                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
-                                        -SWIPE_PADDING_DP));
-                        touchRegion.op(tmpRect, Region.Op.UNION);
-                        mDismissButton.getBoundsOnScreen(tmpRect);
-                        touchRegion.op(tmpRect, Region.Op.UNION);
-                        if (!touchRegion.contains(
+                        if (!mView.isInTouchRegion(
                                 (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
                             mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
                             animateOut();
@@ -513,95 +372,27 @@
         animateOut();
     }
 
-    private void showSinglePreview(View v) {
-        mTextPreview.setVisibility(View.GONE);
-        mImagePreview.setVisibility(View.GONE);
-        mHiddenPreview.setVisibility(View.GONE);
-        v.setVisibility(View.VISIBLE);
-    }
-
-    private void showTextPreview(CharSequence text, TextView textView) {
-        showSinglePreview(textView);
-        final CharSequence truncatedText = text.subSequence(0, Math.min(500, text.length()));
-        textView.setText(truncatedText);
-        updateTextSize(truncatedText, textView);
-
-        textView.addOnLayoutChangeListener(
-                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
-                    if (right - left != oldRight - oldLeft) {
-                        updateTextSize(truncatedText, textView);
-                    }
-                });
-        mEditChip.setVisibility(View.GONE);
-    }
-
-    private void updateTextSize(CharSequence text, TextView textView) {
-        Paint paint = new Paint(textView.getPaint());
-        Resources res = textView.getResources();
-        float minFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_min_font);
-        float maxFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_max_font);
-        if (isOneWord(text) && fitsInView(text, textView, paint, minFontSize)) {
-            // If the text is a single word and would fit within the TextView at the min font size,
-            // find the biggest font size that will fit.
-            float fontSizePx = minFontSize;
-            while (fontSizePx + FONT_SEARCH_STEP_PX < maxFontSize
-                    && fitsInView(text, textView, paint, fontSizePx + FONT_SEARCH_STEP_PX)) {
-                fontSizePx += FONT_SEARCH_STEP_PX;
-            }
-            // Need to turn off autosizing, otherwise setTextSize is a no-op.
-            textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_NONE);
-            // It's possible to hit the max font size and not fill the width, so centering
-            // horizontally looks better in this case.
-            textView.setGravity(Gravity.CENTER);
-            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, (int) fontSizePx);
-        } else {
-            // Otherwise just stick with autosize.
-            textView.setAutoSizeTextTypeUniformWithConfiguration((int) minFontSize,
-                    (int) maxFontSize, FONT_SEARCH_STEP_PX, TypedValue.COMPLEX_UNIT_PX);
-            textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
-        }
-    }
-
-    private static boolean fitsInView(CharSequence text, TextView textView, Paint paint,
-            float fontSizePx) {
-        paint.setTextSize(fontSizePx);
-        float size = paint.measureText(text.toString());
-        float availableWidth = textView.getWidth() - textView.getPaddingLeft()
-                - textView.getPaddingRight();
-        return size < availableWidth;
-    }
-
-    private static boolean isOneWord(CharSequence text) {
-        return text.toString().split("\\s+", 2).length == 1;
-    }
-
     private void showEditableText(CharSequence text, boolean hidden) {
-        TextView textView = hidden ? mHiddenPreview : mTextPreview;
-        showTextPreview(text, textView);
-        View.OnClickListener listener = v -> editText();
-        setAccessibilityActionToEdit(textView);
+        mView.showTextPreview(text, hidden);
+        mView.setEditAccessibilityAction(true);
+        mOnPreviewTapped = this::editText;
         if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
                 CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
-            mEditChip.setVisibility(View.VISIBLE);
-            mActionContainerBackground.setVisibility(View.VISIBLE);
-            mEditChip.setContentDescription(
-                    mContext.getString(R.string.clipboard_edit_text_description));
-            mEditChip.setOnClickListener(listener);
+            mOnEditTapped = this::editText;
+            mView.showEditChip(mContext.getString(R.string.clipboard_edit_text_description));
         }
-        textView.setOnClickListener(listener);
     }
 
     private boolean tryShowEditableImage(Uri uri, boolean isSensitive) {
-        View.OnClickListener listener = v -> editImage(uri);
+        Runnable listener = () -> editImage(uri);
         ContentResolver resolver = mContext.getContentResolver();
         String mimeType = resolver.getType(uri);
         boolean isEditableImage = mimeType != null && mimeType.startsWith("image");
         if (isSensitive) {
-            mHiddenPreview.setText(mContext.getString(R.string.clipboard_text_hidden));
-            showSinglePreview(mHiddenPreview);
+            mView.showImagePreview(null);
             if (isEditableImage) {
-                mHiddenPreview.setOnClickListener(listener);
-                setAccessibilityActionToEdit(mHiddenPreview);
+                mOnPreviewTapped = listener;
+                mView.setEditAccessibilityAction(true);
             }
         } else if (isEditableImage) { // if the MIMEtype is image, try to load
             try {
@@ -609,44 +400,36 @@
                 // The width of the view is capped, height maintains aspect ratio, so allow it to be
                 // taller if needed.
                 Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
-                showSinglePreview(mImagePreview);
-                mImagePreview.setImageBitmap(thumbnail);
-                mImagePreview.setOnClickListener(listener);
-                setAccessibilityActionToEdit(mImagePreview);
+                mView.showImagePreview(thumbnail);
+                mView.setEditAccessibilityAction(true);
+                mOnPreviewTapped = listener;
             } catch (IOException e) {
                 Log.e(TAG, "Thumbnail loading failed", e);
-                showTextPreview(
-                        mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
-                        mTextPreview);
+                mView.showDefaultTextPreview();
                 isEditableImage = false;
             }
         } else {
-            showTextPreview(
-                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
-                    mTextPreview);
+            mView.showDefaultTextPreview();
         }
         if (isEditableImage && DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
-            mEditChip.setVisibility(View.VISIBLE);
-            mActionContainerBackground.setVisibility(View.VISIBLE);
-            mEditChip.setOnClickListener(listener);
-            mEditChip.setContentDescription(
-                    mContext.getString(R.string.clipboard_edit_image_description));
+            mView.showEditChip(mContext.getString(R.string.clipboard_edit_image_description));
         }
         return isEditableImage;
     }
 
-    private void setAccessibilityActionToEdit(View view) {
-        ViewCompat.replaceAccessibilityAction(view,
-                AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
-                mContext.getString(R.string.clipboard_edit), null);
-    }
-
     private void animateIn() {
-        if (mAccessibilityManager.isEnabled()) {
-            mDismissButton.setVisibility(View.VISIBLE);
+        if (mEnterAnimator != null && mEnterAnimator.isRunning()) {
+            return;
         }
-        mEnterAnimator = getEnterAnimation();
+        mEnterAnimator = mView.getEnterAnimation();
+        mEnterAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                mTimeoutHandler.resetTimeout();
+            }
+        });
         mEnterAnimator.start();
     }
 
@@ -654,7 +437,7 @@
         if (mExitAnimator != null && mExitAnimator.isRunning()) {
             return;
         }
-        Animator anim = getExitAnimation();
+        Animator anim = mView.getExitAnimation();
         anim.addListener(new AnimatorListenerAdapter() {
             private boolean mCancelled;
 
@@ -676,122 +459,11 @@
         anim.start();
     }
 
-    private Animator getEnterAnimation() {
-        TimeInterpolator linearInterpolator = new LinearInterpolator();
-        TimeInterpolator scaleInterpolator = new PathInterpolator(0, 0, 0, 1f);
-        AnimatorSet enterAnim = new AnimatorSet();
-
-        ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
-        rootAnim.setInterpolator(linearInterpolator);
-        rootAnim.setDuration(66);
-        rootAnim.addUpdateListener(animation -> {
-            mView.setAlpha(animation.getAnimatedFraction());
-        });
-
-        ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
-        scaleAnim.setInterpolator(scaleInterpolator);
-        scaleAnim.setDuration(333);
-        scaleAnim.addUpdateListener(animation -> {
-            float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
-            mClipboardPreview.setScaleX(previewScale);
-            mClipboardPreview.setScaleY(previewScale);
-            mPreviewBorder.setScaleX(previewScale);
-            mPreviewBorder.setScaleY(previewScale);
-
-            float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
-            mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
-            mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
-            float actionsScaleX = MathUtils.lerp(.7f, 1f, animation.getAnimatedFraction());
-            float actionsScaleY = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
-            mActionContainer.setScaleX(actionsScaleX);
-            mActionContainer.setScaleY(actionsScaleY);
-            mActionContainerBackground.setScaleX(actionsScaleX);
-            mActionContainerBackground.setScaleY(actionsScaleY);
-        });
-
-        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
-        alphaAnim.setInterpolator(linearInterpolator);
-        alphaAnim.setDuration(283);
-        alphaAnim.addUpdateListener(animation -> {
-            float alpha = animation.getAnimatedFraction();
-            mClipboardPreview.setAlpha(alpha);
-            mPreviewBorder.setAlpha(alpha);
-            mDismissButton.setAlpha(alpha);
-            mActionContainer.setAlpha(alpha);
-        });
-
-        mActionContainer.setAlpha(0);
-        mPreviewBorder.setAlpha(0);
-        mClipboardPreview.setAlpha(0);
-        enterAnim.play(rootAnim).with(scaleAnim);
-        enterAnim.play(alphaAnim).after(50).after(rootAnim);
-
-        enterAnim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                super.onAnimationEnd(animation);
-                mView.setAlpha(1);
-                mTimeoutHandler.resetTimeout();
-            }
-        });
-        return enterAnim;
-    }
-
-    private Animator getExitAnimation() {
-        TimeInterpolator linearInterpolator = new LinearInterpolator();
-        TimeInterpolator scaleInterpolator = new PathInterpolator(.3f, 0, 1f, 1f);
-        AnimatorSet exitAnim = new AnimatorSet();
-
-        ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
-        rootAnim.setInterpolator(linearInterpolator);
-        rootAnim.setDuration(100);
-        rootAnim.addUpdateListener(anim -> mView.setAlpha(1 - anim.getAnimatedFraction()));
-
-        ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
-        scaleAnim.setInterpolator(scaleInterpolator);
-        scaleAnim.setDuration(250);
-        scaleAnim.addUpdateListener(animation -> {
-            float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
-            mClipboardPreview.setScaleX(previewScale);
-            mClipboardPreview.setScaleY(previewScale);
-            mPreviewBorder.setScaleX(previewScale);
-            mPreviewBorder.setScaleY(previewScale);
-
-            float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
-            mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
-            mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
-            float actionScaleX = MathUtils.lerp(1f, .8f, animation.getAnimatedFraction());
-            float actionScaleY = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
-            mActionContainer.setScaleX(actionScaleX);
-            mActionContainer.setScaleY(actionScaleY);
-            mActionContainerBackground.setScaleX(actionScaleX);
-            mActionContainerBackground.setScaleY(actionScaleY);
-        });
-
-        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
-        alphaAnim.setInterpolator(linearInterpolator);
-        alphaAnim.setDuration(166);
-        alphaAnim.addUpdateListener(animation -> {
-            float alpha = 1 - animation.getAnimatedFraction();
-            mClipboardPreview.setAlpha(alpha);
-            mPreviewBorder.setAlpha(alpha);
-            mDismissButton.setAlpha(alpha);
-            mActionContainer.setAlpha(alpha);
-        });
-
-        exitAnim.play(alphaAnim).with(scaleAnim);
-        exitAnim.play(rootAnim).after(150).after(alphaAnim);
-        return exitAnim;
-    }
-
-    private void hideImmediate() {
+    void hideImmediate() {
         // Note this may be called multiple times if multiple dismissal events happen at the same
         // time.
         mTimeoutHandler.cancelTimeout();
-        final View decorView = mWindow.peekDecorView();
-        if (decorView != null && decorView.isAttachedToWindow()) {
-            mWindowManager.removeViewImmediate(decorView);
-        }
+        mWindow.remove();
         if (mCloseDialogsReceiver != null) {
             mBroadcastDispatcher.unregisterReceiver(mCloseDialogsReceiver);
             mCloseDialogsReceiver = null;
@@ -813,129 +485,20 @@
         }
     }
 
-    private void resetActionChips() {
-        for (OverlayActionChip chip : mActionChips) {
-            mActionContainer.removeView(chip);
-        }
-        mActionChips.clear();
-    }
-
     private void reset() {
-        mView.setTranslationX(0);
-        mView.setAlpha(0);
-        mActionContainerBackground.setVisibility(View.GONE);
-        mShareChip.setVisibility(View.GONE);
-        mEditChip.setVisibility(View.GONE);
-        mRemoteCopyChip.setVisibility(View.GONE);
-        resetActionChips();
+        mOnRemoteCopyTapped = null;
+        mOnShareTapped = null;
+        mOnEditTapped = null;
+        mOnPreviewTapped = null;
+        mView.reset();
         mTimeoutHandler.cancelTimeout();
         mClipboardLogger.reset();
     }
 
-    @MainThread
-    private void attachWindow() {
-        View decorView = mWindow.getDecorView();
-        if (decorView.isAttachedToWindow() || mBlockAttach) {
-            return;
-        }
-        mBlockAttach = true;
-        mWindowManager.addView(decorView, mWindowLayoutParams);
-        decorView.requestApplyInsets();
-        mView.requestApplyInsets();
-        decorView.getViewTreeObserver().addOnWindowAttachListener(
-                new ViewTreeObserver.OnWindowAttachListener() {
-                    @Override
-                    public void onWindowAttached() {
-                        mBlockAttach = false;
-                    }
-
-                    @Override
-                    public void onWindowDetached() {
-                    }
-                }
-        );
-    }
-
-    private void withWindowAttached(Runnable action) {
-        View decorView = mWindow.getDecorView();
-        if (decorView.isAttachedToWindow()) {
-            action.run();
-        } else {
-            decorView.getViewTreeObserver().addOnWindowAttachListener(
-                    new ViewTreeObserver.OnWindowAttachListener() {
-                        @Override
-                        public void onWindowAttached() {
-                            mBlockAttach = false;
-                            decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
-                            action.run();
-                        }
-
-                        @Override
-                        public void onWindowDetached() {
-                        }
-                    });
-        }
-    }
-
-    private void updateInsets(WindowInsets insets) {
-        int orientation = mContext.getResources().getConfiguration().orientation;
-        FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mView.getLayoutParams();
-        if (p == null) {
-            return;
-        }
-        DisplayCutout cutout = insets.getDisplayCutout();
-        Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
-        Insets imeInsets = insets.getInsets(WindowInsets.Type.ime());
-        if (cutout == null) {
-            p.setMargins(0, 0, 0, Math.max(imeInsets.bottom, navBarInsets.bottom));
-        } else {
-            Insets waterfall = cutout.getWaterfallInsets();
-            if (orientation == ORIENTATION_PORTRAIT) {
-                p.setMargins(
-                        waterfall.left,
-                        Math.max(cutout.getSafeInsetTop(), waterfall.top),
-                        waterfall.right,
-                        Math.max(imeInsets.bottom,
-                                Math.max(cutout.getSafeInsetBottom(),
-                                        Math.max(navBarInsets.bottom, waterfall.bottom))));
-            } else {
-                p.setMargins(
-                        waterfall.left,
-                        waterfall.top,
-                        waterfall.right,
-                        Math.max(imeInsets.bottom,
-                                Math.max(navBarInsets.bottom, waterfall.bottom)));
-            }
-        }
-        mView.setLayoutParams(p);
-        mView.requestLayout();
-    }
-
     private Display getDefaultDisplay() {
         return mDisplayManager.getDisplay(DEFAULT_DISPLAY);
     }
 
-    /**
-     * Updates the window focusability.  If the window is already showing, then it updates the
-     * window immediately, otherwise the layout params will be applied when the window is next
-     * shown.
-     */
-    private void setWindowFocusable(boolean focusable) {
-        int flags = mWindowLayoutParams.flags;
-        if (focusable) {
-            mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-        } else {
-            mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-        }
-        if (mWindowLayoutParams.flags == flags) {
-            return;
-        }
-        final View decorView = mWindow.peekDecorView();
-        if (decorView != null && decorView.isAttachedToWindow()) {
-            mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
-        }
-    }
-
     static class ClipboardLogger {
         private final UiEventLogger mUiEventLogger;
         private boolean mGuarded = false;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java
new file mode 100644
index 0000000..3a040829
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java
@@ -0,0 +1,963 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.clipboardoverlay;
+
+import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EDIT_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
+
+import static java.util.Objects.requireNonNull;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.annotation.MainThread;
+import android.app.ICompatCameraControlCallback;
+import android.app.RemoteAction;
+import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Insets;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.Icon;
+import android.hardware.display.DisplayManager;
+import android.hardware.input.InputManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Looper;
+import android.provider.DeviceConfig;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.Size;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.Gravity;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.InputMonitor;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.PathInterpolator;
+import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.policy.PhoneWindow;
+import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.screenshot.DraggableConstraintLayout;
+import com.android.systemui.screenshot.FloatingWindowUtil;
+import com.android.systemui.screenshot.OverlayActionChip;
+import com.android.systemui.screenshot.TimeoutHandler;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * Controls state and UI for the overlay that appears when something is added to the clipboard
+ */
+public class ClipboardOverlayControllerLegacy implements ClipboardListener.ClipboardOverlay {
+    private static final String TAG = "ClipboardOverlayCtrlr";
+    private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY";
+
+    /** Constants for screenshot/copy deconflicting */
+    public static final String SCREENSHOT_ACTION = "com.android.systemui.SCREENSHOT";
+    public static final String SELF_PERMISSION = "com.android.systemui.permission.SELF";
+    public static final String COPY_OVERLAY_ACTION = "com.android.systemui.COPY";
+
+    private static final String EXTRA_EDIT_SOURCE_CLIPBOARD = "edit_source_clipboard";
+
+    private static final int CLIPBOARD_DEFAULT_TIMEOUT_MILLIS = 6000;
+    private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
+    private static final int FONT_SEARCH_STEP_PX = 4;
+
+    private final Context mContext;
+    private final ClipboardLogger mClipboardLogger;
+    private final BroadcastDispatcher mBroadcastDispatcher;
+    private final DisplayManager mDisplayManager;
+    private final DisplayMetrics mDisplayMetrics;
+    private final WindowManager mWindowManager;
+    private final WindowManager.LayoutParams mWindowLayoutParams;
+    private final PhoneWindow mWindow;
+    private final TimeoutHandler mTimeoutHandler;
+    private final AccessibilityManager mAccessibilityManager;
+    private final TextClassifier mTextClassifier;
+
+    private final DraggableConstraintLayout mView;
+    private final View mClipboardPreview;
+    private final ImageView mImagePreview;
+    private final TextView mTextPreview;
+    private final TextView mHiddenPreview;
+    private final View mPreviewBorder;
+    private final OverlayActionChip mEditChip;
+    private final OverlayActionChip mShareChip;
+    private final OverlayActionChip mRemoteCopyChip;
+    private final View mActionContainerBackground;
+    private final View mDismissButton;
+    private final LinearLayout mActionContainer;
+    private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>();
+
+    private Runnable mOnSessionCompleteListener;
+
+    private InputMonitor mInputMonitor;
+    private InputEventReceiver mInputEventReceiver;
+
+    private BroadcastReceiver mCloseDialogsReceiver;
+    private BroadcastReceiver mScreenshotReceiver;
+
+    private boolean mBlockAttach = false;
+    private Animator mExitAnimator;
+    private Animator mEnterAnimator;
+    private final int mOrientation;
+    private boolean mKeyboardVisible;
+
+
+    public ClipboardOverlayControllerLegacy(Context context,
+            BroadcastDispatcher broadcastDispatcher,
+            BroadcastSender broadcastSender,
+            TimeoutHandler timeoutHandler, UiEventLogger uiEventLogger) {
+        mBroadcastDispatcher = broadcastDispatcher;
+        mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
+        final Context displayContext = context.createDisplayContext(getDefaultDisplay());
+        mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);
+
+        mClipboardLogger = new ClipboardLogger(uiEventLogger);
+
+        mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+        mTextClassifier = requireNonNull(context.getSystemService(TextClassificationManager.class))
+                .getTextClassifier();
+
+        mWindowManager = mContext.getSystemService(WindowManager.class);
+
+        mDisplayMetrics = new DisplayMetrics();
+        mContext.getDisplay().getRealMetrics(mDisplayMetrics);
+
+        mTimeoutHandler = timeoutHandler;
+        mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
+
+        // Setup the window that we are going to use
+        mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
+        mWindowLayoutParams.setTitle("ClipboardOverlay");
+
+        mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
+        mWindow.setWindowManager(mWindowManager, null, null);
+
+        setWindowFocusable(false);
+
+        mView = (DraggableConstraintLayout)
+                LayoutInflater.from(mContext).inflate(R.layout.clipboard_overlay_legacy, null);
+        mActionContainerBackground =
+                requireNonNull(mView.findViewById(R.id.actions_container_background));
+        mActionContainer = requireNonNull(mView.findViewById(R.id.actions));
+        mClipboardPreview = requireNonNull(mView.findViewById(R.id.clipboard_preview));
+        mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview));
+        mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
+        mHiddenPreview = requireNonNull(mView.findViewById(R.id.hidden_preview));
+        mPreviewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
+        mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
+        mShareChip = requireNonNull(mView.findViewById(R.id.share_chip));
+        mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
+        mEditChip.setAlpha(1);
+        mShareChip.setAlpha(1);
+        mRemoteCopyChip.setAlpha(1);
+        mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button));
+
+        mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share));
+        mView.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() {
+            @Override
+            public void onInteraction() {
+                mTimeoutHandler.resetTimeout();
+            }
+
+            @Override
+            public void onSwipeDismissInitiated(Animator animator) {
+                mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
+                mExitAnimator = animator;
+            }
+
+            @Override
+            public void onDismissComplete() {
+                hideImmediate();
+            }
+        });
+
+        mTextPreview.getViewTreeObserver().addOnPreDrawListener(() -> {
+            int availableHeight = mTextPreview.getHeight()
+                    - (mTextPreview.getPaddingTop() + mTextPreview.getPaddingBottom());
+            mTextPreview.setMaxLines(availableHeight / mTextPreview.getLineHeight());
+            return true;
+        });
+
+        mDismissButton.setOnClickListener(view -> {
+            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+            animateOut();
+        });
+
+        mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
+        mRemoteCopyChip.setIcon(
+                Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
+        mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
+        mOrientation = mContext.getResources().getConfiguration().orientation;
+
+        attachWindow();
+        withWindowAttached(() -> {
+            mWindow.setContentView(mView);
+            WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+            mKeyboardVisible = insets.isVisible(WindowInsets.Type.ime());
+            updateInsets(insets);
+            mWindow.peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener(
+                    new ViewTreeObserver.OnGlobalLayoutListener() {
+                        @Override
+                        public void onGlobalLayout() {
+                            WindowInsets insets =
+                                    mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+                            boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime());
+                            if (keyboardVisible != mKeyboardVisible) {
+                                mKeyboardVisible = keyboardVisible;
+                                updateInsets(insets);
+                            }
+                        }
+                    });
+            mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
+                    new ViewRootImpl.ActivityConfigCallback() {
+                        @Override
+                        public void onConfigurationChanged(Configuration overrideConfig,
+                                int newDisplayId) {
+                            if (mContext.getResources().getConfiguration().orientation
+                                    != mOrientation) {
+                                mClipboardLogger.logSessionComplete(
+                                        CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+                                hideImmediate();
+                            }
+                        }
+
+                        @Override
+                        public void requestCompatCameraControl(
+                                boolean showControl, boolean transformationApplied,
+                                ICompatCameraControlCallback callback) {
+                            Log.w(TAG, "unexpected requestCompatCameraControl call");
+                        }
+                    });
+        });
+
+        mTimeoutHandler.setOnTimeoutRunnable(() -> {
+            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TIMED_OUT);
+            animateOut();
+        });
+
+        mCloseDialogsReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+                    mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+                    animateOut();
+                }
+            }
+        };
+
+        mBroadcastDispatcher.registerReceiver(mCloseDialogsReceiver,
+                new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS));
+        mScreenshotReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (SCREENSHOT_ACTION.equals(intent.getAction())) {
+                    mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+                    animateOut();
+                }
+            }
+        };
+
+        mBroadcastDispatcher.registerReceiver(mScreenshotReceiver,
+                new IntentFilter(SCREENSHOT_ACTION), null, null, Context.RECEIVER_EXPORTED,
+                SELF_PERMISSION);
+        monitorOutsideTouches();
+
+        Intent copyIntent = new Intent(COPY_OVERLAY_ACTION);
+        // Set package name so the system knows it's safe
+        copyIntent.setPackage(mContext.getPackageName());
+        broadcastSender.sendBroadcast(copyIntent, SELF_PERMISSION);
+    }
+
+    @Override // ClipboardListener.ClipboardOverlay
+    public void setClipData(ClipData clipData, String clipSource) {
+        if (mExitAnimator != null && mExitAnimator.isRunning()) {
+            mExitAnimator.cancel();
+        }
+        reset();
+        String accessibilityAnnouncement;
+
+        boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null
+                && clipData.getDescription().getExtras()
+                .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
+        if (clipData == null || clipData.getItemCount() == 0) {
+            showTextPreview(
+                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+                    mTextPreview);
+            accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
+        } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
+            ClipData.Item item = clipData.getItemAt(0);
+            if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                    CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
+                if (item.getTextLinks() != null) {
+                    AsyncTask.execute(() -> classifyText(clipData.getItemAt(0), clipSource));
+                }
+            }
+            if (isSensitive) {
+                showEditableText(
+                        mContext.getResources().getString(R.string.clipboard_asterisks), true);
+            } else {
+                showEditableText(item.getText(), false);
+            }
+            showShareChip(clipData);
+            accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied);
+        } else if (clipData.getItemAt(0).getUri() != null) {
+            if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) {
+                showShareChip(clipData);
+                accessibilityAnnouncement = mContext.getString(R.string.clipboard_image_copied);
+            } else {
+                accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
+            }
+        } else {
+            showTextPreview(
+                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+                    mTextPreview);
+            accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
+        }
+        Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext);
+        // Only show remote copy if it's available.
+        PackageManager packageManager = mContext.getPackageManager();
+        if (packageManager.resolveActivity(
+                remoteCopyIntent, PackageManager.ResolveInfoFlags.of(0)) != null) {
+            mRemoteCopyChip.setContentDescription(
+                    mContext.getString(R.string.clipboard_send_nearby_description));
+            mRemoteCopyChip.setVisibility(View.VISIBLE);
+            mRemoteCopyChip.setOnClickListener((v) -> {
+                mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED);
+                mContext.startActivity(remoteCopyIntent);
+                animateOut();
+            });
+            mActionContainerBackground.setVisibility(View.VISIBLE);
+        } else {
+            mRemoteCopyChip.setVisibility(View.GONE);
+        }
+        withWindowAttached(() -> {
+            if (mEnterAnimator == null || !mEnterAnimator.isRunning()) {
+                mView.post(this::animateIn);
+            }
+            mView.announceForAccessibility(accessibilityAnnouncement);
+        });
+        mTimeoutHandler.resetTimeout();
+    }
+
+    @Override // ClipboardListener.ClipboardOverlay
+    public void setOnSessionCompleteListener(Runnable runnable) {
+        mOnSessionCompleteListener = runnable;
+    }
+
+    private void classifyText(ClipData.Item item, String source) {
+        ArrayList<RemoteAction> actions = new ArrayList<>();
+        for (TextLinks.TextLink link : item.getTextLinks().getLinks()) {
+            TextClassification classification = mTextClassifier.classifyText(
+                    item.getText(), link.getStart(), link.getEnd(), null);
+            actions.addAll(classification.getActions());
+        }
+        mView.post(() -> {
+            resetActionChips();
+            if (actions.size() > 0) {
+                mActionContainerBackground.setVisibility(View.VISIBLE);
+                for (RemoteAction action : actions) {
+                    Intent targetIntent = action.getActionIntent().getIntent();
+                    ComponentName component = targetIntent.getComponent();
+                    if (component != null && !TextUtils.equals(source,
+                            component.getPackageName())) {
+                        OverlayActionChip chip = constructActionChip(action);
+                        mActionContainer.addView(chip);
+                        mActionChips.add(chip);
+                        break; // only show at most one action chip
+                    }
+                }
+            }
+        });
+    }
+
+    private void showShareChip(ClipData clip) {
+        mShareChip.setVisibility(View.VISIBLE);
+        mActionContainerBackground.setVisibility(View.VISIBLE);
+        mShareChip.setOnClickListener((v) -> shareContent(clip));
+    }
+
+    private OverlayActionChip constructActionChip(RemoteAction action) {
+        OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate(
+                R.layout.overlay_action_chip, mActionContainer, false);
+        chip.setText(action.getTitle());
+        chip.setContentDescription(action.getTitle());
+        chip.setIcon(action.getIcon(), false);
+        chip.setPendingIntent(action.getActionIntent(), () -> {
+            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
+            animateOut();
+        });
+        chip.setAlpha(1);
+        return chip;
+    }
+
+    private void monitorOutsideTouches() {
+        InputManager inputManager = mContext.getSystemService(InputManager.class);
+        mInputMonitor = inputManager.monitorGestureInput("clipboard overlay", 0);
+        mInputEventReceiver = new InputEventReceiver(mInputMonitor.getInputChannel(),
+                Looper.getMainLooper()) {
+            @Override
+            public void onInputEvent(InputEvent event) {
+                if (event instanceof MotionEvent) {
+                    MotionEvent motionEvent = (MotionEvent) event;
+                    if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                        Region touchRegion = new Region();
+
+                        final Rect tmpRect = new Rect();
+                        mPreviewBorder.getBoundsOnScreen(tmpRect);
+                        tmpRect.inset(
+                                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+                                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
+                                        -SWIPE_PADDING_DP));
+                        touchRegion.op(tmpRect, Region.Op.UNION);
+                        mActionContainerBackground.getBoundsOnScreen(tmpRect);
+                        tmpRect.inset(
+                                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+                                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
+                                        -SWIPE_PADDING_DP));
+                        touchRegion.op(tmpRect, Region.Op.UNION);
+                        mDismissButton.getBoundsOnScreen(tmpRect);
+                        touchRegion.op(tmpRect, Region.Op.UNION);
+                        if (!touchRegion.contains(
+                                (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
+                            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
+                            animateOut();
+                        }
+                    }
+                }
+                finishInputEvent(event, true /* handled */);
+            }
+        };
+    }
+
+    private void editImage(Uri uri) {
+        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
+        mContext.startActivity(IntentCreator.getImageEditIntent(uri, mContext));
+        animateOut();
+    }
+
+    private void editText() {
+        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
+        mContext.startActivity(IntentCreator.getTextEditorIntent(mContext));
+        animateOut();
+    }
+
+    private void shareContent(ClipData clip) {
+        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SHARE_TAPPED);
+        mContext.startActivity(IntentCreator.getShareIntent(clip, mContext));
+        animateOut();
+    }
+
+    private void showSinglePreview(View v) {
+        mTextPreview.setVisibility(View.GONE);
+        mImagePreview.setVisibility(View.GONE);
+        mHiddenPreview.setVisibility(View.GONE);
+        v.setVisibility(View.VISIBLE);
+    }
+
+    private void showTextPreview(CharSequence text, TextView textView) {
+        showSinglePreview(textView);
+        final CharSequence truncatedText = text.subSequence(0, Math.min(500, text.length()));
+        textView.setText(truncatedText);
+        updateTextSize(truncatedText, textView);
+
+        textView.addOnLayoutChangeListener(
+                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                    if (right - left != oldRight - oldLeft) {
+                        updateTextSize(truncatedText, textView);
+                    }
+                });
+        mEditChip.setVisibility(View.GONE);
+    }
+
+    private void updateTextSize(CharSequence text, TextView textView) {
+        Paint paint = new Paint(textView.getPaint());
+        Resources res = textView.getResources();
+        float minFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_min_font);
+        float maxFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_max_font);
+        if (isOneWord(text) && fitsInView(text, textView, paint, minFontSize)) {
+            // If the text is a single word and would fit within the TextView at the min font size,
+            // find the biggest font size that will fit.
+            float fontSizePx = minFontSize;
+            while (fontSizePx + FONT_SEARCH_STEP_PX < maxFontSize
+                    && fitsInView(text, textView, paint, fontSizePx + FONT_SEARCH_STEP_PX)) {
+                fontSizePx += FONT_SEARCH_STEP_PX;
+            }
+            // Need to turn off autosizing, otherwise setTextSize is a no-op.
+            textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_NONE);
+            // It's possible to hit the max font size and not fill the width, so centering
+            // horizontally looks better in this case.
+            textView.setGravity(Gravity.CENTER);
+            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, (int) fontSizePx);
+        } else {
+            // Otherwise just stick with autosize.
+            textView.setAutoSizeTextTypeUniformWithConfiguration((int) minFontSize,
+                    (int) maxFontSize, FONT_SEARCH_STEP_PX, TypedValue.COMPLEX_UNIT_PX);
+            textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
+        }
+    }
+
+    private static boolean fitsInView(CharSequence text, TextView textView, Paint paint,
+            float fontSizePx) {
+        paint.setTextSize(fontSizePx);
+        float size = paint.measureText(text.toString());
+        float availableWidth = textView.getWidth() - textView.getPaddingLeft()
+                - textView.getPaddingRight();
+        return size < availableWidth;
+    }
+
+    private static boolean isOneWord(CharSequence text) {
+        return text.toString().split("\\s+", 2).length == 1;
+    }
+
+    private void showEditableText(CharSequence text, boolean hidden) {
+        TextView textView = hidden ? mHiddenPreview : mTextPreview;
+        showTextPreview(text, textView);
+        View.OnClickListener listener = v -> editText();
+        setAccessibilityActionToEdit(textView);
+        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
+            mEditChip.setVisibility(View.VISIBLE);
+            mActionContainerBackground.setVisibility(View.VISIBLE);
+            mEditChip.setContentDescription(
+                    mContext.getString(R.string.clipboard_edit_text_description));
+            mEditChip.setOnClickListener(listener);
+        }
+        textView.setOnClickListener(listener);
+    }
+
+    private boolean tryShowEditableImage(Uri uri, boolean isSensitive) {
+        View.OnClickListener listener = v -> editImage(uri);
+        ContentResolver resolver = mContext.getContentResolver();
+        String mimeType = resolver.getType(uri);
+        boolean isEditableImage = mimeType != null && mimeType.startsWith("image");
+        if (isSensitive) {
+            mHiddenPreview.setText(mContext.getString(R.string.clipboard_text_hidden));
+            showSinglePreview(mHiddenPreview);
+            if (isEditableImage) {
+                mHiddenPreview.setOnClickListener(listener);
+                setAccessibilityActionToEdit(mHiddenPreview);
+            }
+        } else if (isEditableImage) { // if the MIMEtype is image, try to load
+            try {
+                int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
+                // The width of the view is capped, height maintains aspect ratio, so allow it to be
+                // taller if needed.
+                Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
+                showSinglePreview(mImagePreview);
+                mImagePreview.setImageBitmap(thumbnail);
+                mImagePreview.setOnClickListener(listener);
+                setAccessibilityActionToEdit(mImagePreview);
+            } catch (IOException e) {
+                Log.e(TAG, "Thumbnail loading failed", e);
+                showTextPreview(
+                        mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+                        mTextPreview);
+                isEditableImage = false;
+            }
+        } else {
+            showTextPreview(
+                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+                    mTextPreview);
+        }
+        if (isEditableImage && DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
+            mEditChip.setVisibility(View.VISIBLE);
+            mActionContainerBackground.setVisibility(View.VISIBLE);
+            mEditChip.setOnClickListener(listener);
+            mEditChip.setContentDescription(
+                    mContext.getString(R.string.clipboard_edit_image_description));
+        }
+        return isEditableImage;
+    }
+
+    private void setAccessibilityActionToEdit(View view) {
+        ViewCompat.replaceAccessibilityAction(view,
+                AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+                mContext.getString(R.string.clipboard_edit), null);
+    }
+
+    private void animateIn() {
+        if (mAccessibilityManager.isEnabled()) {
+            mDismissButton.setVisibility(View.VISIBLE);
+        }
+        mEnterAnimator = getEnterAnimation();
+        mEnterAnimator.start();
+    }
+
+    private void animateOut() {
+        if (mExitAnimator != null && mExitAnimator.isRunning()) {
+            return;
+        }
+        Animator anim = getExitAnimation();
+        anim.addListener(new AnimatorListenerAdapter() {
+            private boolean mCancelled;
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                super.onAnimationCancel(animation);
+                mCancelled = true;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                if (!mCancelled) {
+                    hideImmediate();
+                }
+            }
+        });
+        mExitAnimator = anim;
+        anim.start();
+    }
+
+    private Animator getEnterAnimation() {
+        TimeInterpolator linearInterpolator = new LinearInterpolator();
+        TimeInterpolator scaleInterpolator = new PathInterpolator(0, 0, 0, 1f);
+        AnimatorSet enterAnim = new AnimatorSet();
+
+        ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
+        rootAnim.setInterpolator(linearInterpolator);
+        rootAnim.setDuration(66);
+        rootAnim.addUpdateListener(animation -> {
+            mView.setAlpha(animation.getAnimatedFraction());
+        });
+
+        ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
+        scaleAnim.setInterpolator(scaleInterpolator);
+        scaleAnim.setDuration(333);
+        scaleAnim.addUpdateListener(animation -> {
+            float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+            mClipboardPreview.setScaleX(previewScale);
+            mClipboardPreview.setScaleY(previewScale);
+            mPreviewBorder.setScaleX(previewScale);
+            mPreviewBorder.setScaleY(previewScale);
+
+            float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
+            mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
+            mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
+            float actionsScaleX = MathUtils.lerp(.7f, 1f, animation.getAnimatedFraction());
+            float actionsScaleY = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+            mActionContainer.setScaleX(actionsScaleX);
+            mActionContainer.setScaleY(actionsScaleY);
+            mActionContainerBackground.setScaleX(actionsScaleX);
+            mActionContainerBackground.setScaleY(actionsScaleY);
+        });
+
+        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+        alphaAnim.setInterpolator(linearInterpolator);
+        alphaAnim.setDuration(283);
+        alphaAnim.addUpdateListener(animation -> {
+            float alpha = animation.getAnimatedFraction();
+            mClipboardPreview.setAlpha(alpha);
+            mPreviewBorder.setAlpha(alpha);
+            mDismissButton.setAlpha(alpha);
+            mActionContainer.setAlpha(alpha);
+        });
+
+        mActionContainer.setAlpha(0);
+        mPreviewBorder.setAlpha(0);
+        mClipboardPreview.setAlpha(0);
+        enterAnim.play(rootAnim).with(scaleAnim);
+        enterAnim.play(alphaAnim).after(50).after(rootAnim);
+
+        enterAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                mView.setAlpha(1);
+                mTimeoutHandler.resetTimeout();
+            }
+        });
+        return enterAnim;
+    }
+
+    private Animator getExitAnimation() {
+        TimeInterpolator linearInterpolator = new LinearInterpolator();
+        TimeInterpolator scaleInterpolator = new PathInterpolator(.3f, 0, 1f, 1f);
+        AnimatorSet exitAnim = new AnimatorSet();
+
+        ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
+        rootAnim.setInterpolator(linearInterpolator);
+        rootAnim.setDuration(100);
+        rootAnim.addUpdateListener(anim -> mView.setAlpha(1 - anim.getAnimatedFraction()));
+
+        ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
+        scaleAnim.setInterpolator(scaleInterpolator);
+        scaleAnim.setDuration(250);
+        scaleAnim.addUpdateListener(animation -> {
+            float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+            mClipboardPreview.setScaleX(previewScale);
+            mClipboardPreview.setScaleY(previewScale);
+            mPreviewBorder.setScaleX(previewScale);
+            mPreviewBorder.setScaleY(previewScale);
+
+            float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
+            mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
+            mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
+            float actionScaleX = MathUtils.lerp(1f, .8f, animation.getAnimatedFraction());
+            float actionScaleY = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+            mActionContainer.setScaleX(actionScaleX);
+            mActionContainer.setScaleY(actionScaleY);
+            mActionContainerBackground.setScaleX(actionScaleX);
+            mActionContainerBackground.setScaleY(actionScaleY);
+        });
+
+        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+        alphaAnim.setInterpolator(linearInterpolator);
+        alphaAnim.setDuration(166);
+        alphaAnim.addUpdateListener(animation -> {
+            float alpha = 1 - animation.getAnimatedFraction();
+            mClipboardPreview.setAlpha(alpha);
+            mPreviewBorder.setAlpha(alpha);
+            mDismissButton.setAlpha(alpha);
+            mActionContainer.setAlpha(alpha);
+        });
+
+        exitAnim.play(alphaAnim).with(scaleAnim);
+        exitAnim.play(rootAnim).after(150).after(alphaAnim);
+        return exitAnim;
+    }
+
+    private void hideImmediate() {
+        // Note this may be called multiple times if multiple dismissal events happen at the same
+        // time.
+        mTimeoutHandler.cancelTimeout();
+        final View decorView = mWindow.peekDecorView();
+        if (decorView != null && decorView.isAttachedToWindow()) {
+            mWindowManager.removeViewImmediate(decorView);
+        }
+        if (mCloseDialogsReceiver != null) {
+            mBroadcastDispatcher.unregisterReceiver(mCloseDialogsReceiver);
+            mCloseDialogsReceiver = null;
+        }
+        if (mScreenshotReceiver != null) {
+            mBroadcastDispatcher.unregisterReceiver(mScreenshotReceiver);
+            mScreenshotReceiver = null;
+        }
+        if (mInputEventReceiver != null) {
+            mInputEventReceiver.dispose();
+            mInputEventReceiver = null;
+        }
+        if (mInputMonitor != null) {
+            mInputMonitor.dispose();
+            mInputMonitor = null;
+        }
+        if (mOnSessionCompleteListener != null) {
+            mOnSessionCompleteListener.run();
+        }
+    }
+
+    private void resetActionChips() {
+        for (OverlayActionChip chip : mActionChips) {
+            mActionContainer.removeView(chip);
+        }
+        mActionChips.clear();
+    }
+
+    private void reset() {
+        mView.setTranslationX(0);
+        mView.setAlpha(0);
+        mActionContainerBackground.setVisibility(View.GONE);
+        mShareChip.setVisibility(View.GONE);
+        mEditChip.setVisibility(View.GONE);
+        mRemoteCopyChip.setVisibility(View.GONE);
+        resetActionChips();
+        mTimeoutHandler.cancelTimeout();
+        mClipboardLogger.reset();
+    }
+
+    @MainThread
+    private void attachWindow() {
+        View decorView = mWindow.getDecorView();
+        if (decorView.isAttachedToWindow() || mBlockAttach) {
+            return;
+        }
+        mBlockAttach = true;
+        mWindowManager.addView(decorView, mWindowLayoutParams);
+        decorView.requestApplyInsets();
+        mView.requestApplyInsets();
+        decorView.getViewTreeObserver().addOnWindowAttachListener(
+                new ViewTreeObserver.OnWindowAttachListener() {
+                    @Override
+                    public void onWindowAttached() {
+                        mBlockAttach = false;
+                    }
+
+                    @Override
+                    public void onWindowDetached() {
+                    }
+                }
+        );
+    }
+
+    private void withWindowAttached(Runnable action) {
+        View decorView = mWindow.getDecorView();
+        if (decorView.isAttachedToWindow()) {
+            action.run();
+        } else {
+            decorView.getViewTreeObserver().addOnWindowAttachListener(
+                    new ViewTreeObserver.OnWindowAttachListener() {
+                        @Override
+                        public void onWindowAttached() {
+                            mBlockAttach = false;
+                            decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
+                            action.run();
+                        }
+
+                        @Override
+                        public void onWindowDetached() {
+                        }
+                    });
+        }
+    }
+
+    private void updateInsets(WindowInsets insets) {
+        int orientation = mContext.getResources().getConfiguration().orientation;
+        FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mView.getLayoutParams();
+        if (p == null) {
+            return;
+        }
+        DisplayCutout cutout = insets.getDisplayCutout();
+        Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
+        Insets imeInsets = insets.getInsets(WindowInsets.Type.ime());
+        if (cutout == null) {
+            p.setMargins(0, 0, 0, Math.max(imeInsets.bottom, navBarInsets.bottom));
+        } else {
+            Insets waterfall = cutout.getWaterfallInsets();
+            if (orientation == ORIENTATION_PORTRAIT) {
+                p.setMargins(
+                        waterfall.left,
+                        Math.max(cutout.getSafeInsetTop(), waterfall.top),
+                        waterfall.right,
+                        Math.max(imeInsets.bottom,
+                                Math.max(cutout.getSafeInsetBottom(),
+                                        Math.max(navBarInsets.bottom, waterfall.bottom))));
+            } else {
+                p.setMargins(
+                        waterfall.left,
+                        waterfall.top,
+                        waterfall.right,
+                        Math.max(imeInsets.bottom,
+                                Math.max(navBarInsets.bottom, waterfall.bottom)));
+            }
+        }
+        mView.setLayoutParams(p);
+        mView.requestLayout();
+    }
+
+    private Display getDefaultDisplay() {
+        return mDisplayManager.getDisplay(DEFAULT_DISPLAY);
+    }
+
+    /**
+     * Updates the window focusability.  If the window is already showing, then it updates the
+     * window immediately, otherwise the layout params will be applied when the window is next
+     * shown.
+     */
+    private void setWindowFocusable(boolean focusable) {
+        int flags = mWindowLayoutParams.flags;
+        if (focusable) {
+            mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        } else {
+            mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        }
+        if (mWindowLayoutParams.flags == flags) {
+            return;
+        }
+        final View decorView = mWindow.peekDecorView();
+        if (decorView != null && decorView.isAttachedToWindow()) {
+            mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
+        }
+    }
+
+    static class ClipboardLogger {
+        private final UiEventLogger mUiEventLogger;
+        private boolean mGuarded = false;
+
+        ClipboardLogger(UiEventLogger uiEventLogger) {
+            mUiEventLogger = uiEventLogger;
+        }
+
+        void logSessionComplete(@NonNull UiEventLogger.UiEventEnum event) {
+            if (!mGuarded) {
+                mGuarded = true;
+                mUiEventLogger.log(event);
+            }
+        }
+
+        void reset() {
+            mGuarded = false;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerFactory.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java
similarity index 76%
rename from packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerFactory.java
rename to packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java
index 8b0b2a5..0d989a7 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java
@@ -27,17 +27,17 @@
 import javax.inject.Inject;
 
 /**
- * A factory that churns out ClipboardOverlayControllers on demand.
+ * A factory that churns out ClipboardOverlayControllerLegacys on demand.
  */
 @SysUISingleton
-public class ClipboardOverlayControllerFactory {
+public class ClipboardOverlayControllerLegacyFactory {
 
     private final UiEventLogger mUiEventLogger;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final BroadcastSender mBroadcastSender;
 
     @Inject
-    public ClipboardOverlayControllerFactory(BroadcastDispatcher broadcastDispatcher,
+    public ClipboardOverlayControllerLegacyFactory(BroadcastDispatcher broadcastDispatcher,
             BroadcastSender broadcastSender, UiEventLogger uiEventLogger) {
         this.mBroadcastDispatcher = broadcastDispatcher;
         this.mBroadcastSender = broadcastSender;
@@ -45,10 +45,10 @@
     }
 
     /**
-     * One new ClipboardOverlayController, coming right up!
+     * One new ClipboardOverlayControllerLegacy, coming right up!
      */
-    public ClipboardOverlayController create(Context context) {
-        return new ClipboardOverlayController(context, mBroadcastDispatcher, mBroadcastSender,
+    public ClipboardOverlayControllerLegacy create(Context context) {
+        return new ClipboardOverlayControllerLegacy(context, mBroadcastDispatcher, mBroadcastSender,
                 new TimeoutHandler(context), mUiEventLogger);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
new file mode 100644
index 0000000..2d33157
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -0,0 +1,482 @@
+/*
+ * 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.clipboardoverlay;
+
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
+import static java.util.Objects.requireNonNull;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
+import android.app.RemoteAction;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Insets;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.Icon;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.MathUtils;
+import android.util.TypedValue;
+import android.view.DisplayCutout;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.PathInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.core.view.ViewCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+
+import com.android.systemui.R;
+import com.android.systemui.screenshot.DraggableConstraintLayout;
+import com.android.systemui.screenshot.FloatingWindowUtil;
+import com.android.systemui.screenshot.OverlayActionChip;
+
+import java.util.ArrayList;
+
+/**
+ * Handles the visual elements and animations for the clipboard overlay.
+ */
+public class ClipboardOverlayView extends DraggableConstraintLayout {
+
+    interface ClipboardOverlayCallbacks extends SwipeDismissCallbacks {
+        void onDismissButtonTapped();
+
+        void onRemoteCopyButtonTapped();
+
+        void onEditButtonTapped();
+
+        void onShareButtonTapped();
+
+        void onPreviewTapped();
+    }
+
+    private static final String TAG = "ClipboardView";
+
+    private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
+    private static final int FONT_SEARCH_STEP_PX = 4;
+
+    private final DisplayMetrics mDisplayMetrics;
+    private final AccessibilityManager mAccessibilityManager;
+    private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>();
+
+    private View mClipboardPreview;
+    private ImageView mImagePreview;
+    private TextView mTextPreview;
+    private TextView mHiddenPreview;
+    private View mPreviewBorder;
+    private OverlayActionChip mEditChip;
+    private OverlayActionChip mShareChip;
+    private OverlayActionChip mRemoteCopyChip;
+    private View mActionContainerBackground;
+    private View mDismissButton;
+    private LinearLayout mActionContainer;
+
+    public ClipboardOverlayView(Context context) {
+        this(context, null);
+    }
+
+    public ClipboardOverlayView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ClipboardOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mDisplayMetrics = new DisplayMetrics();
+        mContext.getDisplay().getRealMetrics(mDisplayMetrics);
+        mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        mActionContainerBackground =
+                requireNonNull(findViewById(R.id.actions_container_background));
+        mActionContainer = requireNonNull(findViewById(R.id.actions));
+        mClipboardPreview = requireNonNull(findViewById(R.id.clipboard_preview));
+        mImagePreview = requireNonNull(findViewById(R.id.image_preview));
+        mTextPreview = requireNonNull(findViewById(R.id.text_preview));
+        mHiddenPreview = requireNonNull(findViewById(R.id.hidden_preview));
+        mPreviewBorder = requireNonNull(findViewById(R.id.preview_border));
+        mEditChip = requireNonNull(findViewById(R.id.edit_chip));
+        mShareChip = requireNonNull(findViewById(R.id.share_chip));
+        mRemoteCopyChip = requireNonNull(findViewById(R.id.remote_copy_chip));
+        mDismissButton = requireNonNull(findViewById(R.id.dismiss_button));
+
+        mEditChip.setAlpha(1);
+        mShareChip.setAlpha(1);
+        mRemoteCopyChip.setAlpha(1);
+        mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share));
+
+        mEditChip.setIcon(
+                Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
+        mRemoteCopyChip.setIcon(
+                Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
+        mShareChip.setIcon(
+                Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
+
+        mRemoteCopyChip.setContentDescription(
+                mContext.getString(R.string.clipboard_send_nearby_description));
+
+        mTextPreview.getViewTreeObserver().addOnPreDrawListener(() -> {
+            int availableHeight = mTextPreview.getHeight()
+                    - (mTextPreview.getPaddingTop() + mTextPreview.getPaddingBottom());
+            mTextPreview.setMaxLines(availableHeight / mTextPreview.getLineHeight());
+            return true;
+        });
+        super.onFinishInflate();
+    }
+
+    @Override
+    public void setCallbacks(SwipeDismissCallbacks callbacks) {
+        super.setCallbacks(callbacks);
+        ClipboardOverlayCallbacks clipboardCallbacks = (ClipboardOverlayCallbacks) callbacks;
+        mEditChip.setOnClickListener(v -> clipboardCallbacks.onEditButtonTapped());
+        mShareChip.setOnClickListener(v -> clipboardCallbacks.onShareButtonTapped());
+        mDismissButton.setOnClickListener(v -> clipboardCallbacks.onDismissButtonTapped());
+        mRemoteCopyChip.setOnClickListener(v -> clipboardCallbacks.onRemoteCopyButtonTapped());
+        mClipboardPreview.setOnClickListener(v -> clipboardCallbacks.onPreviewTapped());
+    }
+
+    void setEditAccessibilityAction(boolean editable) {
+        if (editable) {
+            ViewCompat.replaceAccessibilityAction(mClipboardPreview,
+                    AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+                    mContext.getString(R.string.clipboard_edit), null);
+        } else {
+            ViewCompat.replaceAccessibilityAction(mClipboardPreview,
+                    AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+                    null, null);
+        }
+    }
+
+    void setInsets(WindowInsets insets, int orientation) {
+        FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) getLayoutParams();
+        if (p == null) {
+            return;
+        }
+        Rect margins = computeMargins(insets, orientation);
+        p.setMargins(margins.left, margins.top, margins.right, margins.bottom);
+        setLayoutParams(p);
+        requestLayout();
+    }
+
+    boolean isInTouchRegion(int x, int y) {
+        Region touchRegion = new Region();
+        final Rect tmpRect = new Rect();
+
+        mPreviewBorder.getBoundsOnScreen(tmpRect);
+        tmpRect.inset(
+                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
+        touchRegion.op(tmpRect, Region.Op.UNION);
+
+        mActionContainerBackground.getBoundsOnScreen(tmpRect);
+        tmpRect.inset(
+                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
+        touchRegion.op(tmpRect, Region.Op.UNION);
+
+        mDismissButton.getBoundsOnScreen(tmpRect);
+        touchRegion.op(tmpRect, Region.Op.UNION);
+
+        return touchRegion.contains(x, y);
+    }
+
+    void setRemoteCopyVisibility(boolean visible) {
+        if (visible) {
+            mRemoteCopyChip.setVisibility(View.VISIBLE);
+            mActionContainerBackground.setVisibility(View.VISIBLE);
+        } else {
+            mRemoteCopyChip.setVisibility(View.GONE);
+        }
+    }
+
+    void showDefaultTextPreview() {
+        String copied = mContext.getString(R.string.clipboard_overlay_text_copied);
+        showTextPreview(copied, false);
+    }
+
+    void showTextPreview(CharSequence text, boolean hidden) {
+        TextView textView = hidden ? mHiddenPreview : mTextPreview;
+        showSinglePreview(textView);
+        textView.setText(text.subSequence(0, Math.min(500, text.length())));
+        updateTextSize(text, textView);
+        textView.addOnLayoutChangeListener(
+                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                    if (right - left != oldRight - oldLeft) {
+                        updateTextSize(text, textView);
+                    }
+                });
+        mEditChip.setVisibility(View.GONE);
+    }
+
+    void showImagePreview(@Nullable Bitmap thumbnail) {
+        if (thumbnail == null) {
+            mHiddenPreview.setText(mContext.getString(R.string.clipboard_text_hidden));
+            showSinglePreview(mHiddenPreview);
+        } else {
+            mImagePreview.setImageBitmap(thumbnail);
+            showSinglePreview(mImagePreview);
+        }
+    }
+
+    void showEditChip(String contentDescription) {
+        mEditChip.setVisibility(View.VISIBLE);
+        mActionContainerBackground.setVisibility(View.VISIBLE);
+        mEditChip.setContentDescription(contentDescription);
+    }
+
+    void showShareChip() {
+        mShareChip.setVisibility(View.VISIBLE);
+        mActionContainerBackground.setVisibility(View.VISIBLE);
+    }
+
+    void reset() {
+        setTranslationX(0);
+        setAlpha(0);
+        mActionContainerBackground.setVisibility(View.GONE);
+        mDismissButton.setVisibility(View.GONE);
+        mShareChip.setVisibility(View.GONE);
+        mEditChip.setVisibility(View.GONE);
+        mRemoteCopyChip.setVisibility(View.GONE);
+        setEditAccessibilityAction(false);
+        resetActionChips();
+    }
+
+    void resetActionChips() {
+        for (OverlayActionChip chip : mActionChips) {
+            mActionContainer.removeView(chip);
+        }
+        mActionChips.clear();
+    }
+
+    Animator getEnterAnimation() {
+        if (mAccessibilityManager.isEnabled()) {
+            mDismissButton.setVisibility(View.VISIBLE);
+        }
+        TimeInterpolator linearInterpolator = new LinearInterpolator();
+        TimeInterpolator scaleInterpolator = new PathInterpolator(0, 0, 0, 1f);
+        AnimatorSet enterAnim = new AnimatorSet();
+
+        ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
+        rootAnim.setInterpolator(linearInterpolator);
+        rootAnim.setDuration(66);
+        rootAnim.addUpdateListener(animation -> {
+            setAlpha(animation.getAnimatedFraction());
+        });
+
+        ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
+        scaleAnim.setInterpolator(scaleInterpolator);
+        scaleAnim.setDuration(333);
+        scaleAnim.addUpdateListener(animation -> {
+            float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+            mClipboardPreview.setScaleX(previewScale);
+            mClipboardPreview.setScaleY(previewScale);
+            mPreviewBorder.setScaleX(previewScale);
+            mPreviewBorder.setScaleY(previewScale);
+
+            float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
+            mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
+            mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
+            float actionsScaleX = MathUtils.lerp(.7f, 1f, animation.getAnimatedFraction());
+            float actionsScaleY = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+            mActionContainer.setScaleX(actionsScaleX);
+            mActionContainer.setScaleY(actionsScaleY);
+            mActionContainerBackground.setScaleX(actionsScaleX);
+            mActionContainerBackground.setScaleY(actionsScaleY);
+        });
+
+        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+        alphaAnim.setInterpolator(linearInterpolator);
+        alphaAnim.setDuration(283);
+        alphaAnim.addUpdateListener(animation -> {
+            float alpha = animation.getAnimatedFraction();
+            mClipboardPreview.setAlpha(alpha);
+            mPreviewBorder.setAlpha(alpha);
+            mDismissButton.setAlpha(alpha);
+            mActionContainer.setAlpha(alpha);
+        });
+
+        mActionContainer.setAlpha(0);
+        mPreviewBorder.setAlpha(0);
+        mClipboardPreview.setAlpha(0);
+        enterAnim.play(rootAnim).with(scaleAnim);
+        enterAnim.play(alphaAnim).after(50).after(rootAnim);
+
+        enterAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                setAlpha(1);
+            }
+        });
+        return enterAnim;
+    }
+
+    Animator getExitAnimation() {
+        TimeInterpolator linearInterpolator = new LinearInterpolator();
+        TimeInterpolator scaleInterpolator = new PathInterpolator(.3f, 0, 1f, 1f);
+        AnimatorSet exitAnim = new AnimatorSet();
+
+        ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
+        rootAnim.setInterpolator(linearInterpolator);
+        rootAnim.setDuration(100);
+        rootAnim.addUpdateListener(anim -> setAlpha(1 - anim.getAnimatedFraction()));
+
+        ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
+        scaleAnim.setInterpolator(scaleInterpolator);
+        scaleAnim.setDuration(250);
+        scaleAnim.addUpdateListener(animation -> {
+            float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+            mClipboardPreview.setScaleX(previewScale);
+            mClipboardPreview.setScaleY(previewScale);
+            mPreviewBorder.setScaleX(previewScale);
+            mPreviewBorder.setScaleY(previewScale);
+
+            float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
+            mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
+            mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
+            float actionScaleX = MathUtils.lerp(1f, .8f, animation.getAnimatedFraction());
+            float actionScaleY = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+            mActionContainer.setScaleX(actionScaleX);
+            mActionContainer.setScaleY(actionScaleY);
+            mActionContainerBackground.setScaleX(actionScaleX);
+            mActionContainerBackground.setScaleY(actionScaleY);
+        });
+
+        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+        alphaAnim.setInterpolator(linearInterpolator);
+        alphaAnim.setDuration(166);
+        alphaAnim.addUpdateListener(animation -> {
+            float alpha = 1 - animation.getAnimatedFraction();
+            mClipboardPreview.setAlpha(alpha);
+            mPreviewBorder.setAlpha(alpha);
+            mDismissButton.setAlpha(alpha);
+            mActionContainer.setAlpha(alpha);
+        });
+
+        exitAnim.play(alphaAnim).with(scaleAnim);
+        exitAnim.play(rootAnim).after(150).after(alphaAnim);
+        return exitAnim;
+    }
+
+    void setActionChip(RemoteAction action, Runnable onFinish) {
+        mActionContainerBackground.setVisibility(View.VISIBLE);
+        OverlayActionChip chip = constructActionChip(action, onFinish);
+        mActionContainer.addView(chip);
+        mActionChips.add(chip);
+    }
+
+    private void showSinglePreview(View v) {
+        mTextPreview.setVisibility(View.GONE);
+        mImagePreview.setVisibility(View.GONE);
+        mHiddenPreview.setVisibility(View.GONE);
+        v.setVisibility(View.VISIBLE);
+    }
+
+    private OverlayActionChip constructActionChip(RemoteAction action, Runnable onFinish) {
+        OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate(
+                R.layout.overlay_action_chip, mActionContainer, false);
+        chip.setText(action.getTitle());
+        chip.setContentDescription(action.getTitle());
+        chip.setIcon(action.getIcon(), false);
+        chip.setPendingIntent(action.getActionIntent(), onFinish);
+        chip.setAlpha(1);
+        return chip;
+    }
+
+    private static void updateTextSize(CharSequence text, TextView textView) {
+        Paint paint = new Paint(textView.getPaint());
+        Resources res = textView.getResources();
+        float minFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_min_font);
+        float maxFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_max_font);
+        if (isOneWord(text) && fitsInView(text, textView, paint, minFontSize)) {
+            // If the text is a single word and would fit within the TextView at the min font size,
+            // find the biggest font size that will fit.
+            float fontSizePx = minFontSize;
+            while (fontSizePx + FONT_SEARCH_STEP_PX < maxFontSize
+                    && fitsInView(text, textView, paint, fontSizePx + FONT_SEARCH_STEP_PX)) {
+                fontSizePx += FONT_SEARCH_STEP_PX;
+            }
+            // Need to turn off autosizing, otherwise setTextSize is a no-op.
+            textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_NONE);
+            // It's possible to hit the max font size and not fill the width, so centering
+            // horizontally looks better in this case.
+            textView.setGravity(Gravity.CENTER);
+            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, (int) fontSizePx);
+        } else {
+            // Otherwise just stick with autosize.
+            textView.setAutoSizeTextTypeUniformWithConfiguration((int) minFontSize,
+                    (int) maxFontSize, FONT_SEARCH_STEP_PX, TypedValue.COMPLEX_UNIT_PX);
+            textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
+        }
+    }
+
+    private static boolean fitsInView(CharSequence text, TextView textView, Paint paint,
+            float fontSizePx) {
+        paint.setTextSize(fontSizePx);
+        float size = paint.measureText(text.toString());
+        float availableWidth = textView.getWidth() - textView.getPaddingLeft()
+                - textView.getPaddingRight();
+        return size < availableWidth;
+    }
+
+    private static boolean isOneWord(CharSequence text) {
+        return text.toString().split("\\s+", 2).length == 1;
+    }
+
+    private static Rect computeMargins(WindowInsets insets, int orientation) {
+        DisplayCutout cutout = insets.getDisplayCutout();
+        Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
+        Insets imeInsets = insets.getInsets(WindowInsets.Type.ime());
+        if (cutout == null) {
+            return new Rect(0, 0, 0, Math.max(imeInsets.bottom, navBarInsets.bottom));
+        } else {
+            Insets waterfall = cutout.getWaterfallInsets();
+            if (orientation == ORIENTATION_PORTRAIT) {
+                return new Rect(
+                        waterfall.left,
+                        Math.max(cutout.getSafeInsetTop(), waterfall.top),
+                        waterfall.right,
+                        Math.max(imeInsets.bottom,
+                                Math.max(cutout.getSafeInsetBottom(),
+                                        Math.max(navBarInsets.bottom, waterfall.bottom))));
+            } else {
+                return new Rect(
+                        waterfall.left,
+                        waterfall.top,
+                        waterfall.right,
+                        Math.max(imeInsets.bottom,
+                                Math.max(navBarInsets.bottom, waterfall.bottom)));
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java
new file mode 100644
index 0000000..9dac9b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java
@@ -0,0 +1,174 @@
+/*
+ * 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.clipboardoverlay;
+
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.app.ICompatCameraControlCallback;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+import com.android.internal.policy.PhoneWindow;
+import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext;
+import com.android.systemui.screenshot.FloatingWindowUtil;
+
+import java.util.function.BiConsumer;
+
+import javax.inject.Inject;
+
+/**
+ * Handles attaching the window and the window insets for the clipboard overlay.
+ */
+public class ClipboardOverlayWindow extends PhoneWindow
+        implements ViewRootImpl.ActivityConfigCallback {
+    private static final String TAG = "ClipboardOverlayWindow";
+
+    private final Context mContext;
+    private final WindowManager mWindowManager;
+    private final WindowManager.LayoutParams mWindowLayoutParams;
+
+    private boolean mKeyboardVisible;
+    private final int mOrientation;
+    private BiConsumer<WindowInsets, Integer> mOnKeyboardChangeListener;
+    private Runnable mOnOrientationChangeListener;
+
+    @Inject
+    ClipboardOverlayWindow(@OverlayWindowContext Context context) {
+        super(context);
+        mContext = context;
+        mOrientation = mContext.getResources().getConfiguration().orientation;
+
+        // Setup the window that we are going to use
+        requestFeature(Window.FEATURE_NO_TITLE);
+        requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);
+        setBackgroundDrawableResource(android.R.color.transparent);
+        mWindowManager = mContext.getSystemService(WindowManager.class);
+        mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
+        mWindowLayoutParams.setTitle("ClipboardOverlay");
+        setWindowManager(mWindowManager, null, null);
+        setWindowFocusable(false);
+    }
+
+    /**
+     * Set callbacks for keyboard state change and orientation change and attach the window
+     *
+     * @param onKeyboardChangeListener callback for IME visibility changes
+     * @param onOrientationChangeListener callback for device orientation changes
+     */
+    public void init(@NonNull BiConsumer<WindowInsets, Integer> onKeyboardChangeListener,
+            @NonNull Runnable onOrientationChangeListener) {
+        mOnKeyboardChangeListener = onKeyboardChangeListener;
+        mOnOrientationChangeListener = onOrientationChangeListener;
+
+        attach();
+        withWindowAttached(() -> {
+            WindowInsets currentInsets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+            mKeyboardVisible = currentInsets.isVisible(WindowInsets.Type.ime());
+            peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener(() -> {
+                WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+                boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime());
+                if (keyboardVisible != mKeyboardVisible) {
+                    mKeyboardVisible = keyboardVisible;
+                    mOnKeyboardChangeListener.accept(insets, mOrientation);
+                }
+            });
+            peekDecorView().getViewRootImpl().setActivityConfigCallback(this);
+        });
+    }
+
+    @Override // ViewRootImpl.ActivityConfigCallback
+    public void onConfigurationChanged(Configuration overrideConfig, int newDisplayId) {
+        if (mContext.getResources().getConfiguration().orientation != mOrientation) {
+            mOnOrientationChangeListener.run();
+        }
+    }
+
+    @Override // ViewRootImpl.ActivityConfigCallback
+    public void requestCompatCameraControl(boolean showControl, boolean transformationApplied,
+            ICompatCameraControlCallback callback) {
+        Log.w(TAG, "unexpected requestCompatCameraControl call");
+    }
+
+    void remove() {
+        final View decorView = peekDecorView();
+        if (decorView != null && decorView.isAttachedToWindow()) {
+            mWindowManager.removeViewImmediate(decorView);
+        }
+    }
+
+    WindowInsets getWindowInsets() {
+        return mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+    }
+
+    void withWindowAttached(Runnable action) {
+        View decorView = getDecorView();
+        if (decorView.isAttachedToWindow()) {
+            action.run();
+        } else {
+            decorView.getViewTreeObserver().addOnWindowAttachListener(
+                    new ViewTreeObserver.OnWindowAttachListener() {
+                        @Override
+                        public void onWindowAttached() {
+                            decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
+                            action.run();
+                        }
+
+                        @Override
+                        public void onWindowDetached() {
+                        }
+                    });
+        }
+    }
+
+    @MainThread
+    private void attach() {
+        View decorView = getDecorView();
+        if (decorView.isAttachedToWindow()) {
+            return;
+        }
+        mWindowManager.addView(decorView, mWindowLayoutParams);
+        decorView.requestApplyInsets();
+    }
+
+    /**
+     * Updates the window focusability.  If the window is already showing, then it updates the
+     * window immediately, otherwise the layout params will be applied when the window is next
+     * shown.
+     */
+    private void setWindowFocusable(boolean focusable) {
+        int flags = mWindowLayoutParams.flags;
+        if (focusable) {
+            mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        } else {
+            mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        }
+        if (mWindowLayoutParams.flags == flags) {
+            return;
+        }
+        final View decorView = peekDecorView();
+        if (decorView != null && decorView.isAttachedToWindow()) {
+            mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
new file mode 100644
index 0000000..2244813
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
@@ -0,0 +1,68 @@
+/*
+ * 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.clipboardoverlay.dagger;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+import android.view.LayoutInflater;
+
+import com.android.systemui.R;
+import com.android.systemui.clipboardoverlay.ClipboardOverlayView;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Module for {@link com.android.systemui.clipboardoverlay}. */
+@Module
+public interface ClipboardOverlayModule {
+
+    /**
+     *
+     */
+    @Provides
+    @OverlayWindowContext
+    static Context provideWindowContext(DisplayManager displayManager, Context context) {
+        Display display = displayManager.getDisplay(DEFAULT_DISPLAY);
+        return context.createWindowContext(display, TYPE_SCREENSHOT, null);
+    }
+
+    /**
+     *
+     */
+    @Provides
+    static ClipboardOverlayView provideClipboardOverlayView(@OverlayWindowContext Context context) {
+        return (ClipboardOverlayView) LayoutInflater.from(context).inflate(
+                R.layout.clipboard_overlay, null);
+    }
+
+    @Qualifier
+    @Documented
+    @Retention(RUNTIME)
+    @interface OverlayWindowContext {
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt
index bebade0..08e8293 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.common.shared.model
 
 import android.annotation.StringRes
+import android.content.Context
 
 /**
  * Models a content description, that can either be already [loaded][ContentDescription.Loaded] or
@@ -30,4 +31,20 @@
     data class Resource(
         @StringRes val res: Int,
     ) : ContentDescription()
+
+    companion object {
+        /**
+         * Returns the loaded content description string, or null if we don't have one.
+         *
+         * Prefer [com.android.systemui.common.ui.binder.ContentDescriptionViewBinder.bind] over
+         * this method. This should only be used for testing or concatenation purposes.
+         */
+        fun ContentDescription?.loadContentDescription(context: Context): String? {
+            return when (this) {
+                null -> null
+                is Loaded -> this.description
+                is Resource -> context.getString(this.res)
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/Text.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Text.kt
index 5d0e08f..4a56932 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/Text.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Text.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.common.shared.model
 
 import android.annotation.StringRes
+import android.content.Context
 
 /**
  * Models a text, that can either be already [loaded][Text.Loaded] or be a [reference]
@@ -31,4 +32,20 @@
     data class Resource(
         @StringRes val res: Int,
     ) : Text()
+
+    companion object {
+        /**
+         * Returns the loaded test string, or null if we don't have one.
+         *
+         * Prefer [com.android.systemui.common.ui.binder.TextViewBinder.bind] over this method. This
+         * should only be used for testing or concatenation purposes.
+         */
+        fun Text?.loadText(context: Context): String? {
+            return when (this) {
+                null -> null
+                is Loaded -> this.text
+                is Resource -> context.getString(this.res)
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index 77b6523..d3b5d0e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -21,6 +21,8 @@
 import android.content.Intent
 import android.content.IntentFilter
 import android.os.Bundle
+import android.os.RemoteException
+import android.service.dreams.IDreamManager
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowInsets
@@ -40,11 +42,13 @@
  */
 class ControlsActivity @Inject constructor(
     private val uiController: ControlsUiController,
-    private val broadcastDispatcher: BroadcastDispatcher
+    private val broadcastDispatcher: BroadcastDispatcher,
+    private val dreamManager: IDreamManager,
 ) : ComponentActivity() {
 
     private lateinit var parent: ViewGroup
     private lateinit var broadcastReceiver: BroadcastReceiver
+    private var mExitToDream: Boolean = false
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -81,17 +85,36 @@
 
         parent = requireViewById<ViewGroup>(R.id.global_actions_controls)
         parent.alpha = 0f
-        uiController.show(parent, { finish() }, this)
+        uiController.show(parent, { finishOrReturnToDream() }, this)
 
         ControlsAnimations.enterAnimation(parent).start()
     }
 
-    override fun onBackPressed() {
+    override fun onResume() {
+        super.onResume()
+        mExitToDream = intent.getBooleanExtra(ControlsUiController.EXIT_TO_DREAM, false)
+    }
+
+    fun finishOrReturnToDream() {
+        if (mExitToDream) {
+            try {
+                mExitToDream = false
+                dreamManager.dream()
+                return
+            } catch (e: RemoteException) {
+                // Fall through
+            }
+        }
         finish()
     }
 
+    override fun onBackPressed() {
+        finishOrReturnToDream()
+    }
+
     override fun onStop() {
         super.onStop()
+        mExitToDream = false
 
         uiController.hide()
     }
@@ -106,7 +129,8 @@
         broadcastReceiver = object : BroadcastReceiver() {
             override fun onReceive(context: Context, intent: Intent) {
                 val action = intent.getAction()
-                if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+                if (action == Intent.ACTION_SCREEN_OFF ||
+                    action == Intent.ACTION_DREAMING_STARTED) {
                     finish()
                 }
             }
@@ -114,6 +138,7 @@
 
         val filter = IntentFilter()
         filter.addAction(Intent.ACTION_SCREEN_OFF)
+        filter.addAction(Intent.ACTION_DREAMING_STARTED)
         broadcastDispatcher.registerReceiver(broadcastReceiver, filter)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index 822f8f2..c1cfbcb 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -27,6 +27,7 @@
     companion object {
         public const val TAG = "ControlsUiController"
         public const val EXTRA_ANIMATE = "extra_animate"
+        public const val EXIT_TO_DREAM = "extra_exit_to_dream"
     }
 
     fun show(parent: ViewGroup, onDismiss: Runnable, activityContext: Context)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 55eda0a..721c0ba 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -34,13 +34,14 @@
 import com.android.systemui.media.RingtonePlayer
 import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
-import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender
+import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
 import com.android.systemui.power.PowerUI
 import com.android.systemui.recents.Recents
 import com.android.systemui.settings.dagger.MultiUserUtilsModule
 import com.android.systemui.shortcut.ShortcutKeyDispatcher
 import com.android.systemui.statusbar.notification.InstantAppNotifier
 import com.android.systemui.statusbar.phone.KeyguardLiftController
+import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import com.android.systemui.theme.ThemeOverlayController
 import com.android.systemui.toast.ToastUI
 import com.android.systemui.usb.StorageNotification
@@ -217,6 +218,12 @@
     @ClassKey(KeyguardLiftController::class)
     abstract fun bindKeyguardLiftController(sysui: KeyguardLiftController): CoreStartable
 
+    /** Inject into MediaTttSenderCoordinator. */
+    @Binds
+    @IntoMap
+    @ClassKey(MediaTttSenderCoordinator::class)
+    abstract fun bindMediaTttSenderCoordinator(sysui: MediaTttSenderCoordinator): CoreStartable
+
     /** Inject into MediaTttChipControllerReceiver. */
     @Binds
     @IntoMap
@@ -225,17 +232,15 @@
             sysui: MediaTttChipControllerReceiver
     ): CoreStartable
 
-    /** Inject into MediaTttChipControllerSender. */
-    @Binds
-    @IntoMap
-    @ClassKey(MediaTttChipControllerSender::class)
-    abstract fun bindMediaTttChipControllerSender(
-            sysui: MediaTttChipControllerSender
-    ): CoreStartable
-
     /** Inject into MediaTttCommandLineHelper. */
     @Binds
     @IntoMap
     @ClassKey(MediaTttCommandLineHelper::class)
     abstract fun bindMediaTttCommandLineHelper(sysui: MediaTttCommandLineHelper): CoreStartable
+
+    /** Inject into ChipbarCoordinator. */
+    @Binds
+    @IntoMap
+    @ClassKey(ChipbarCoordinator::class)
+    abstract fun bindChipbarController(sysui: ChipbarCoordinator): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index dc3dadb..7e31626 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -23,7 +23,8 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.keyguard.clock.ClockModule;
+import com.android.keyguard.clock.ClockInfoModule;
+import com.android.keyguard.dagger.ClockRegistryModule;
 import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.BootCompleteCache;
 import com.android.systemui.BootCompleteCacheImpl;
@@ -33,6 +34,7 @@
 import com.android.systemui.biometrics.UdfpsDisplayModeProvider;
 import com.android.systemui.biometrics.dagger.BiometricsModule;
 import com.android.systemui.classifier.FalsingModule;
+import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
 import com.android.systemui.controls.dagger.ControlsModule;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.demomode.dagger.DemoModeModule;
@@ -118,7 +120,9 @@
             AssistModule.class,
             BiometricsModule.class,
             BouncerViewModule.class,
-            ClockModule.class,
+            ClipboardOverlayModule.class,
+            ClockInfoModule.class,
+            ClockRegistryModule.class,
             CoroutinesModule.class,
             DreamModule.class,
             ControlsModule.class,
@@ -165,12 +169,16 @@
     @Binds
     abstract BootCompleteCache bindBootCompleteCache(BootCompleteCacheImpl bootCompleteCache);
 
-    /** */
+    /**
+     *
+     */
     @Binds
     public abstract ContextComponentHelper bindComponentHelper(
             ContextComponentResolver componentHelper);
 
-    /** */
+    /**
+     *
+     */
     @Binds
     public abstract NotificationRowBinder bindNotificationRowBinder(
             NotificationRowBinderImpl notificationRowBinder);
@@ -209,6 +217,7 @@
     abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
 
     // TODO: This should provided by the WM component
+
     /** Provides Optional of BubbleManager */
     @SysUISingleton
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt
index 991b54e..ded0fb7 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt
@@ -59,7 +59,7 @@
         (view as? DisplayCutoutView)?.let { cutoutView ->
             cutoutView.setColor(tintColor)
             cutoutView.updateRotation(rotation)
-            cutoutView.onDisplayChanged(displayUniqueId)
+            cutoutView.updateConfiguration(displayUniqueId)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index ec0013b..976afd4 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -34,8 +34,6 @@
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import java.util.concurrent.Executor
 import javax.inject.Inject
@@ -47,15 +45,13 @@
     private val statusBarStateController: StatusBarStateController,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     @Main private val mainExecutor: Executor,
-    private val featureFlags: FeatureFlags
 ) : DecorProviderFactory() {
     private val display = context.display
     private val displayInfo = DisplayInfo()
 
     override val hasProviders: Boolean
         get() {
-            if (!featureFlags.isEnabled(Flags.FACE_SCANNING_ANIM) ||
-                    authController.faceSensorLocation == null) {
+            if (authController.faceSensorLocation == null) {
                 return false
             }
 
@@ -99,7 +95,7 @@
     }
 
     fun shouldShowFaceScanningAnim(): Boolean {
-        return canShowFaceScanningAnim() && keyguardUpdateMonitor.isFaceScanning
+        return canShowFaceScanningAnim() && keyguardUpdateMonitor.isFaceDetectionRunning
     }
 }
 
@@ -124,7 +120,7 @@
             view.layoutParams = it
             (view as? FaceScanningOverlay)?.let { overlay ->
                 overlay.setColor(tintColor)
-                overlay.onDisplayChanged(displayUniqueId)
+                overlay.updateConfiguration(displayUniqueId)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
index a252864..8b4aeef 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
@@ -78,23 +78,18 @@
         reloadMeasures()
     }
 
-    private fun reloadAll(newReloadToken: Int) {
-        if (reloadToken == newReloadToken) {
-            return
-        }
-        reloadToken = newReloadToken
-        reloadRes()
-        reloadMeasures()
-    }
-
     fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) {
         if (displayUniqueId != newDisplayUniqueId) {
             displayUniqueId = newDisplayUniqueId
             newReloadToken ?.let { reloadToken = it }
             reloadRes()
             reloadMeasures()
-        } else {
-            newReloadToken?.let { reloadAll(it) }
+        } else if (newReloadToken != null) {
+            if (reloadToken == newReloadToken) {
+                return
+            }
+            reloadToken = newReloadToken
+            reloadMeasures()
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 2e51b51..b69afeb 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -287,8 +287,8 @@
     /**
      * Appends sensor event dropped event to logs
      */
-    public void traceSensorEventDropped(int sensorEvent, String reason) {
-        mLogger.logSensorEventDropped(sensorEvent, reason);
+    public void traceSensorEventDropped(@Reason int pulseReason, String reason) {
+        mLogger.logSensorEventDropped(pulseReason, reason);
     }
 
     /**
@@ -386,6 +386,47 @@
         mLogger.logSetAodDimmingScrim((long) scrimOpacity);
     }
 
+    /**
+     * Appends sensor attempted to register and whether it was a successful registration.
+     */
+    public void traceSensorRegisterAttempt(String sensorName, boolean successfulRegistration) {
+        mLogger.logSensorRegisterAttempt(sensorName, successfulRegistration);
+    }
+
+    /**
+     * Appends sensor attempted to unregister and whether it was successfully unregistered.
+     */
+    public void traceSensorUnregisterAttempt(String sensorInfo, boolean successfullyUnregistered) {
+        mLogger.logSensorUnregisterAttempt(sensorInfo, successfullyUnregistered);
+    }
+
+    /**
+     * Appends sensor attempted to unregister and whether it was successfully unregistered
+     * with a reason the sensor is being unregistered.
+     */
+    public void traceSensorUnregisterAttempt(String sensorInfo, boolean successfullyUnregistered,
+            String reason) {
+        mLogger.logSensorUnregisterAttempt(sensorInfo, successfullyUnregistered, reason);
+    }
+
+    /**
+     * Appends the event of skipping a sensor registration since it's already registered.
+     */
+    public void traceSkipRegisterSensor(String sensorInfo) {
+        mLogger.logSkipSensorRegistration(sensorInfo);
+    }
+
+    /**
+     * Appends a plugin sensor was registered or unregistered event.
+     */
+    public void tracePluginSensorUpdate(boolean registered) {
+        if (registered) {
+            mLogger.log("register plugin sensor");
+        } else {
+            mLogger.log("unregister plugin sensor");
+        }
+    }
+
     private class SummaryStats {
         private int mCount;
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index cc57662..18c8e01 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -19,12 +19,13 @@
 import android.view.Display
 import com.android.systemui.doze.DozeLog.Reason
 import com.android.systemui.doze.DozeLog.reasonToString
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.ERROR
+import com.android.systemui.plugins.log.LogLevel.INFO
 import com.android.systemui.log.dagger.DozeLog
 import com.android.systemui.statusbar.policy.DevicePostureController
+import com.google.errorprone.annotations.CompileTimeConstant
 import java.text.SimpleDateFormat
 import java.util.Date
 import java.util.Locale
@@ -224,10 +225,14 @@
         })
     }
 
-    fun logPulseDropped(from: String, state: DozeMachine.State) {
+    /**
+     * Log why a pulse was dropped and the current doze machine state. The state can be null
+     * if the DozeMachine is the middle of transitioning between states.
+     */
+    fun logPulseDropped(from: String, state: DozeMachine.State?) {
         buffer.log(TAG, INFO, {
             str1 = from
-            str2 = state.name
+            str2 = state?.name
         }, {
             "Pulse dropped, cannot pulse from=$str1 state=$str2"
         })
@@ -320,6 +325,50 @@
             "Doze car mode started"
         })
     }
+
+    fun logSensorRegisterAttempt(sensorInfo: String, successfulRegistration: Boolean) {
+        buffer.log(TAG, INFO, {
+            str1 = sensorInfo
+            bool1 = successfulRegistration
+        }, {
+            "Register sensor. Success=$bool1 sensor=$str1"
+        })
+    }
+
+    fun logSensorUnregisterAttempt(sensorInfo: String, successfulUnregister: Boolean) {
+        buffer.log(TAG, INFO, {
+            str1 = sensorInfo
+            bool1 = successfulUnregister
+        }, {
+            "Unregister sensor. Success=$bool1 sensor=$str1"
+        })
+    }
+
+    fun logSensorUnregisterAttempt(
+            sensorInfo: String,
+            successfulUnregister: Boolean,
+            reason: String
+    ) {
+        buffer.log(TAG, INFO, {
+            str1 = sensorInfo
+            bool1 = successfulUnregister
+            str2 = reason
+        }, {
+            "Unregister sensor. reason=$str2. Success=$bool1 sensor=$str1"
+        })
+    }
+
+    fun logSkipSensorRegistration(sensor: String) {
+        buffer.log(TAG, DEBUG, {
+            str1 = sensor
+        }, {
+            "Skipping sensor registration because its already registered. sensor=$str1"
+        })
+    }
+
+    fun log(@CompileTimeConstant msg: String) {
+        buffer.log(TAG, DEBUG, msg)
+    }
 }
 
 private const val TAG = "DozeLog"
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index ae41215..96c35d4 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -20,7 +20,6 @@
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
 
 import android.annotation.MainThread;
-import android.app.UiModeManager;
 import android.content.res.Configuration;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.Trace;
@@ -145,10 +144,9 @@
 
     private final Service mDozeService;
     private final WakeLock mWakeLock;
-    private final AmbientDisplayConfiguration mConfig;
+    private final AmbientDisplayConfiguration mAmbientDisplayConfig;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
     private final DozeHost mDozeHost;
-    private final UiModeManager mUiModeManager;
     private final DockManager mDockManager;
     private final Part[] mParts;
 
@@ -156,18 +154,18 @@
     private State mState = State.UNINITIALIZED;
     private int mPulseReason;
     private boolean mWakeLockHeldForCurrentState = false;
+    private int mUiModeType = Configuration.UI_MODE_TYPE_NORMAL;
 
     @Inject
-    public DozeMachine(@WrappedService Service service, AmbientDisplayConfiguration config,
+    public DozeMachine(@WrappedService Service service,
+            AmbientDisplayConfiguration ambientDisplayConfig,
             WakeLock wakeLock, WakefulnessLifecycle wakefulnessLifecycle,
-            UiModeManager uiModeManager,
             DozeLog dozeLog, DockManager dockManager,
             DozeHost dozeHost, Part[] parts) {
         mDozeService = service;
-        mConfig = config;
+        mAmbientDisplayConfig = ambientDisplayConfig;
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mWakeLock = wakeLock;
-        mUiModeManager = uiModeManager;
         mDozeLog = dozeLog;
         mDockManager = dockManager;
         mDozeHost = dozeHost;
@@ -187,6 +185,18 @@
     }
 
     /**
+     * Notifies the {@link DozeMachine} that {@link Configuration} has changed.
+     */
+    public void onConfigurationChanged(Configuration newConfiguration) {
+        int newUiModeType = newConfiguration.uiMode & Configuration.UI_MODE_TYPE_MASK;
+        if (mUiModeType == newUiModeType) return;
+        mUiModeType = newUiModeType;
+        for (Part part : mParts) {
+            part.onUiModeTypeChanged(mUiModeType);
+        }
+    }
+
+    /**
      * Requests transitioning to {@code requestedState}.
      *
      * This can be called during a state transition, in which case it will be queued until all
@@ -211,6 +221,14 @@
         requestState(State.DOZE_REQUEST_PULSE, pulseReason);
     }
 
+    /**
+     * @return true if {@link DozeMachine} is currently in either {@link State#UNINITIALIZED}
+     *  or {@link State#FINISH}
+     */
+    public boolean isUninitializedOrFinished() {
+        return mState == State.UNINITIALIZED || mState == State.FINISH;
+    }
+
     void onScreenState(int state) {
         mDozeLog.traceDisplayState(state);
         for (Part part : mParts) {
@@ -360,7 +378,7 @@
         if (mState == State.FINISH) {
             return State.FINISH;
         }
-        if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR
+        if (mUiModeType == Configuration.UI_MODE_TYPE_CAR
                 && (requestedState.canPulse() || requestedState.staysAwake())) {
             Log.i(TAG, "Doze is suppressed with all triggers disabled as car mode is active");
             mDozeLog.traceCarModeStarted();
@@ -411,7 +429,7 @@
                     nextState = State.FINISH;
                 } else if (mDockManager.isDocked()) {
                     nextState = mDockManager.isHidden() ? State.DOZE : State.DOZE_AOD_DOCKED;
-                } else if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) {
+                } else if (mAmbientDisplayConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) {
                     nextState = State.DOZE_AOD;
                 } else {
                     nextState = State.DOZE;
@@ -427,6 +445,7 @@
     /** Dumps the current state */
     public void dump(PrintWriter pw) {
         pw.print(" state="); pw.println(mState);
+        pw.print(" mUiModeType="); pw.println(mUiModeType);
         pw.print(" wakeLockHeldForCurrentState="); pw.println(mWakeLockHeldForCurrentState);
         pw.print(" wakeLock="); pw.println(mWakeLock);
         pw.println("Parts:");
@@ -459,6 +478,19 @@
 
         /** Sets the {@link DozeMachine} when this Part is associated with one. */
         default void setDozeMachine(DozeMachine dozeMachine) {}
+
+        /**
+         * Notifies the Part about a change in {@link Configuration#uiMode}.
+         *
+         * @param newUiModeType {@link Configuration#UI_MODE_TYPE_NORMAL},
+         *                   {@link Configuration#UI_MODE_TYPE_DESK},
+         *                   {@link Configuration#UI_MODE_TYPE_CAR},
+         *                   {@link Configuration#UI_MODE_TYPE_TELEVISION},
+         *                   {@link Configuration#UI_MODE_TYPE_APPLIANCE},
+         *                   {@link Configuration#UI_MODE_TYPE_WATCH},
+         *                   or {@link Configuration#UI_MODE_TYPE_VR_HEADSET}
+         */
+        default void onUiModeTypeChanged(int newUiModeType) {}
     }
 
     /** A wrapper interface for {@link android.service.dreams.DreamService} */
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index da6c163..9a091e7 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -23,7 +23,6 @@
 
 import android.annotation.AnyThread;
 import android.app.ActivityManager;
-import android.content.Context;
 import android.database.ContentObserver;
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
@@ -37,7 +36,6 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.IndentingPrintWriter;
-import android.util.Log;
 import android.view.Display;
 
 import androidx.annotation.NonNull;
@@ -88,12 +86,9 @@
  * trigger callbacks on the provided {@link mProxCallback}.
  */
 public class DozeSensors {
-
-    private static final boolean DEBUG = DozeService.DEBUG;
     private static final String TAG = "DozeSensors";
     private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
 
-    private final Context mContext;
     private final AsyncSensorManager mSensorManager;
     private final AmbientDisplayConfiguration mConfig;
     private final WakeLock mWakeLock;
@@ -144,7 +139,6 @@
     }
 
     DozeSensors(
-            Context context,
             AsyncSensorManager sensorManager,
             DozeParameters dozeParameters,
             AmbientDisplayConfiguration config,
@@ -157,7 +151,6 @@
             AuthController authController,
             DevicePostureController devicePostureController
     ) {
-        mContext = context;
         mSensorManager = sensorManager;
         mConfig = config;
         mWakeLock = wakeLock;
@@ -605,10 +598,7 @@
             // cancel the previous sensor:
             if (mRegistered) {
                 final boolean rt = mSensorManager.cancelTriggerSensor(this, oldSensor);
-                if (DEBUG) {
-                    Log.d(TAG, "posture changed, cancelTriggerSensor[" + oldSensor + "] "
-                            + rt);
-                }
+                mDozeLog.traceSensorUnregisterAttempt(oldSensor.toString(), rt, "posture changed");
                 mRegistered = false;
             }
 
@@ -654,19 +644,13 @@
             if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)) {
                 if (!mRegistered) {
                     mRegistered = mSensorManager.requestTriggerSensor(this, sensor);
-                    if (DEBUG) {
-                        Log.d(TAG, "requestTriggerSensor[" + sensor + "] " + mRegistered);
-                    }
+                    mDozeLog.traceSensorRegisterAttempt(sensor.toString(), mRegistered);
                 } else {
-                    if (DEBUG) {
-                        Log.d(TAG, "requestTriggerSensor[" + sensor + "] already registered");
-                    }
+                    mDozeLog.traceSkipRegisterSensor(sensor.toString());
                 }
             } else if (mRegistered) {
                 final boolean rt = mSensorManager.cancelTriggerSensor(this, sensor);
-                if (DEBUG) {
-                    Log.d(TAG, "cancelTriggerSensor[" + sensor + "] " + rt);
-                }
+                mDozeLog.traceSensorUnregisterAttempt(sensor.toString(), rt);
                 mRegistered = false;
             }
         }
@@ -704,7 +688,6 @@
             final Sensor sensor = mSensors[mPosture];
             mDozeLog.traceSensor(mPulseReason);
             mHandler.post(mWakeLock.wrap(() -> {
-                if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event));
                 if (sensor != null && sensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) {
                     UI_EVENT_LOGGER.log(DozeSensorsUiEvent.ACTION_AMBIENT_GESTURE_PICKUP);
                 }
@@ -776,11 +759,11 @@
                     && !mRegistered) {
                 asyncSensorManager.registerPluginListener(mPluginSensor, this);
                 mRegistered = true;
-                if (DEBUG) Log.d(TAG, "registerPluginListener");
+                mDozeLog.tracePluginSensorUpdate(true /* registered */);
             } else if (mRegistered) {
                 asyncSensorManager.unregisterPluginListener(mPluginSensor, this);
                 mRegistered = false;
-                if (DEBUG) Log.d(TAG, "unregisterPluginListener");
+                mDozeLog.tracePluginSensorUpdate(false /* registered */);
             }
         }
 
@@ -813,10 +796,9 @@
             mHandler.post(mWakeLock.wrap(() -> {
                 final long now = SystemClock.uptimeMillis();
                 if (now < mDebounceFrom + mDebounce) {
-                    Log.d(TAG, "onSensorEvent dropped: " + triggerEventToString(event));
+                    mDozeLog.traceSensorEventDropped(mPulseReason, "debounce");
                     return;
                 }
-                if (DEBUG) Log.d(TAG, "onSensorEvent: " + triggerEventToString(event));
                 mSensorCallback.onSensorPulse(mPulseReason, -1, -1, event.getValues());
             }));
         }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index a2eb4e3..e8d7e46 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -17,6 +17,7 @@
 package com.android.systemui.doze;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.service.dreams.DreamService;
@@ -59,6 +60,7 @@
         mPluginManager.addPluginListener(this, DozeServicePlugin.class, false /* allowMultiple */);
         DozeComponent dozeComponent = mDozeComponentBuilder.build(this);
         mDozeMachine = dozeComponent.getDozeMachine();
+        mDozeMachine.onConfigurationChanged(getResources().getConfiguration());
     }
 
     @Override
@@ -127,6 +129,12 @@
     }
 
     @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mDozeMachine.onConfigurationChanged(newConfig);
+    }
+
+    @Override
     public void onRequestHideDoze() {
         if (mDozeMachine != null) {
             mDozeMachine.requestState(DozeMachine.State.DOZE);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java
index 7ed4b35..e6d9865 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java
@@ -16,21 +16,13 @@
 
 package com.android.systemui.doze;
 
-import static android.app.UiModeManager.ACTION_ENTER_CAR_MODE;
-import static android.app.UiModeManager.ACTION_EXIT_CAR_MODE;
+import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
 
-import android.app.UiModeManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Configuration;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.PowerManager;
 import android.os.UserHandle;
 import android.text.TextUtils;
 
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.doze.dagger.DozeScope;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 
@@ -43,7 +35,9 @@
 /**
  * Handles suppressing doze on:
  * 1. INITIALIZED, don't allow dozing at all when:
- *      - in CAR_MODE
+ *      - in CAR_MODE, in this scenario the device is asleep and won't listen for any triggers
+ *      to wake up. In this state, no UI shows. Unlike other conditions, this suppression is only
+ *      temporary and stops when the device exits CAR_MODE
  *      - device is NOT provisioned
  *      - there's a pending authentication
  * 2. PowerSaveMode active
@@ -57,35 +51,47 @@
  */
 @DozeScope
 public class DozeSuppressor implements DozeMachine.Part {
-    private static final String TAG = "DozeSuppressor";
 
     private DozeMachine mMachine;
     private final DozeHost mDozeHost;
     private final AmbientDisplayConfiguration mConfig;
     private final DozeLog mDozeLog;
-    private final BroadcastDispatcher mBroadcastDispatcher;
-    private final UiModeManager mUiModeManager;
     private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
 
-    private boolean mBroadcastReceiverRegistered;
+    private boolean mIsCarModeEnabled = false;
 
     @Inject
     public DozeSuppressor(
             DozeHost dozeHost,
             AmbientDisplayConfiguration config,
             DozeLog dozeLog,
-            BroadcastDispatcher broadcastDispatcher,
-            UiModeManager uiModeManager,
             Lazy<BiometricUnlockController> biometricUnlockControllerLazy) {
         mDozeHost = dozeHost;
         mConfig = config;
         mDozeLog = dozeLog;
-        mBroadcastDispatcher = broadcastDispatcher;
-        mUiModeManager = uiModeManager;
         mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
     }
 
     @Override
+    public void onUiModeTypeChanged(int newUiModeType) {
+        boolean isCarModeEnabled = newUiModeType == UI_MODE_TYPE_CAR;
+        if (mIsCarModeEnabled == isCarModeEnabled) {
+            return;
+        }
+        mIsCarModeEnabled = isCarModeEnabled;
+        // Do not handle the event if doze machine is not initialized yet.
+        // It will be handled upon initialization.
+        if (mMachine.isUninitializedOrFinished()) {
+            return;
+        }
+        if (mIsCarModeEnabled) {
+            handleCarModeStarted();
+        } else {
+            handleCarModeExited();
+        }
+    }
+
+    @Override
     public void setDozeMachine(DozeMachine dozeMachine) {
         mMachine = dozeMachine;
     }
@@ -94,7 +100,6 @@
     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
         switch (newState) {
             case INITIALIZED:
-                registerBroadcastReceiver();
                 mDozeHost.addCallback(mHostCallback);
                 checkShouldImmediatelyEndDoze();
                 checkShouldImmediatelySuspendDoze();
@@ -108,14 +113,12 @@
 
     @Override
     public void destroy() {
-        unregisterBroadcastReceiver();
         mDozeHost.removeCallback(mHostCallback);
     }
 
     private void checkShouldImmediatelySuspendDoze() {
-        if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) {
-            mDozeLog.traceCarModeStarted();
-            mMachine.requestState(DozeMachine.State.DOZE_SUSPEND_TRIGGERS);
+        if (mIsCarModeEnabled) {
+            handleCarModeStarted();
         }
     }
 
@@ -135,7 +138,7 @@
 
     @Override
     public void dump(PrintWriter pw) {
-        pw.println(" uiMode=" + mUiModeManager.getCurrentModeType());
+        pw.println(" isCarModeEnabled=" + mIsCarModeEnabled);
         pw.println(" hasPendingAuth="
                 + mBiometricUnlockControllerLazy.get().hasPendingAuthentication());
         pw.println(" isProvisioned=" + mDozeHost.isProvisioned());
@@ -143,40 +146,18 @@
         pw.println(" aodPowerSaveActive=" + mDozeHost.isPowerSaveActive());
     }
 
-    private void registerBroadcastReceiver() {
-        if (mBroadcastReceiverRegistered) {
-            return;
-        }
-        IntentFilter filter = new IntentFilter(ACTION_ENTER_CAR_MODE);
-        filter.addAction(ACTION_EXIT_CAR_MODE);
-        mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter);
-        mBroadcastReceiverRegistered = true;
+    private void handleCarModeExited() {
+        mDozeLog.traceCarModeEnded();
+        mMachine.requestState(mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)
+                ? DozeMachine.State.DOZE_AOD : DozeMachine.State.DOZE);
     }
 
-    private void unregisterBroadcastReceiver() {
-        if (!mBroadcastReceiverRegistered) {
-            return;
-        }
-        mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
-        mBroadcastReceiverRegistered = false;
+    private void handleCarModeStarted() {
+        mDozeLog.traceCarModeStarted();
+        mMachine.requestState(DozeMachine.State.DOZE_SUSPEND_TRIGGERS);
     }
 
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (ACTION_ENTER_CAR_MODE.equals(action)) {
-                mDozeLog.traceCarModeStarted();
-                mMachine.requestState(DozeMachine.State.DOZE_SUSPEND_TRIGGERS);
-            } else if (ACTION_EXIT_CAR_MODE.equals(action)) {
-                mDozeLog.traceCarModeEnded();
-                mMachine.requestState(mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)
-                        ? DozeMachine.State.DOZE_AOD : DozeMachine.State.DOZE);
-            }
-        }
-    };
-
-    private DozeHost.Callback mHostCallback = new DozeHost.Callback() {
+    private final DozeHost.Callback mHostCallback = new DozeHost.Callback() {
         @Override
         public void onPowerSaveChanged(boolean active) {
             // handles suppression changes, while DozeMachine#transitionPolicy handles gating
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index ef454ff..32cb1c0 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -198,7 +198,7 @@
         mAllowPulseTriggers = true;
         mSessionTracker = sessionTracker;
 
-        mDozeSensors = new DozeSensors(context, mSensorManager, dozeParameters,
+        mDozeSensors = new DozeSensors(mSensorManager, dozeParameters,
                 config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor,
                 secureSettings, authController, devicePostureController);
         mDockManager = dockManager;
@@ -536,13 +536,13 @@
             return;
         }
 
-        if (!mAllowPulseTriggers || mDozeHost.isPulsePending() || !canPulse()) {
+        if (!mAllowPulseTriggers || mDozeHost.isPulsePending() || !canPulse(dozeState)) {
             if (!mAllowPulseTriggers) {
                 mDozeLog.tracePulseDropped("requestPulse - !mAllowPulseTriggers");
             } else if (mDozeHost.isPulsePending()) {
                 mDozeLog.tracePulseDropped("requestPulse - pulsePending");
-            } else if (!canPulse()) {
-                mDozeLog.tracePulseDropped("requestPulse", dozeState);
+            } else if (!canPulse(dozeState)) {
+                mDozeLog.tracePulseDropped("requestPulse - dozeState cannot pulse", dozeState);
             }
             runIfNotNull(onPulseSuppressedListener);
             return;
@@ -559,15 +559,16 @@
                 // not in pocket, continue pulsing
                 final boolean isPulsePending = mDozeHost.isPulsePending();
                 mDozeHost.setPulsePending(false);
-                if (!isPulsePending || mDozeHost.isPulsingBlocked() || !canPulse()) {
+                if (!isPulsePending || mDozeHost.isPulsingBlocked() || !canPulse(dozeState)) {
                     if (!isPulsePending) {
                         mDozeLog.tracePulseDropped("continuePulseRequest - pulse no longer"
                                 + " pending, pulse was cancelled before it could start"
                                 + " transitioning to pulsing state.");
                     } else if (mDozeHost.isPulsingBlocked()) {
                         mDozeLog.tracePulseDropped("continuePulseRequest - pulsingBlocked");
-                    } else if (!canPulse()) {
-                        mDozeLog.tracePulseDropped("continuePulseRequest", mMachine.getState());
+                    } else if (!canPulse(dozeState)) {
+                        mDozeLog.tracePulseDropped("continuePulseRequest"
+                                + " - doze state cannot pulse", dozeState);
                     }
                     runIfNotNull(onPulseSuppressedListener);
                     return;
@@ -582,10 +583,10 @@
                 .ifPresent(uiEventEnum -> mUiEventLogger.log(uiEventEnum, getKeyguardSessionId()));
     }
 
-    private boolean canPulse() {
-        return mMachine.getState() == DozeMachine.State.DOZE
-                || mMachine.getState() == DozeMachine.State.DOZE_AOD
-                || mMachine.getState() == DozeMachine.State.DOZE_AOD_DOCKED;
+    private boolean canPulse(DozeMachine.State dozeState) {
+        return dozeState == DozeMachine.State.DOZE
+                || dozeState == DozeMachine.State.DOZE_AOD
+                || dozeState == DozeMachine.State.DOZE_AOD_DOCKED;
     }
 
     @Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index 0ccb222..cedd850a 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -210,7 +210,8 @@
 
             final Intent intent = new Intent(mContext, ControlsActivity.class)
                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
-                    .putExtra(ControlsUiController.EXTRA_ANIMATE, true);
+                    .putExtra(ControlsUiController.EXTRA_ANIMATE, true)
+                    .putExtra(ControlsUiController.EXIT_TO_DREAM, true);
 
             final ActivityLaunchAnimator.Controller controller =
                     v != null ? ActivityLaunchAnimator.Controller.fromView(v, null /* cujType */)
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java
index c07d402..1166c2f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java
@@ -28,7 +28,7 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.complication.dagger.DreamMediaEntryComplicationComponent;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.MediaCarouselController;
+import com.android.systemui.media.controls.ui.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/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
index 08ef8f3..609bd76 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -24,8 +24,13 @@
 import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL
 import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_HIGH
 import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_NORMAL
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.dump.nano.SystemUIProtoDump
+import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager
+import com.google.protobuf.nano.MessageNano
+import java.io.BufferedOutputStream
+import java.io.FileDescriptor
+import java.io.FileOutputStream
 import java.io.PrintWriter
 import javax.inject.Inject
 import javax.inject.Provider
@@ -100,7 +105,7 @@
     /**
      * Dump the diagnostics! Behavior can be controlled via [args].
      */
-    fun dump(pw: PrintWriter, args: Array<String>) {
+    fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
         Trace.beginSection("DumpManager#dump()")
         val start = SystemClock.uptimeMillis()
 
@@ -111,10 +116,12 @@
             return
         }
 
-        when (parsedArgs.dumpPriority) {
-            PRIORITY_ARG_CRITICAL -> dumpCritical(pw, parsedArgs)
-            PRIORITY_ARG_NORMAL -> dumpNormal(pw, parsedArgs)
-            else -> dumpParameterized(pw, parsedArgs)
+        when {
+            parsedArgs.dumpPriority == PRIORITY_ARG_CRITICAL -> dumpCritical(pw, parsedArgs)
+            parsedArgs.dumpPriority == PRIORITY_ARG_NORMAL && !parsedArgs.proto -> {
+                dumpNormal(pw, parsedArgs)
+            }
+            else -> dumpParameterized(fd, pw, parsedArgs)
         }
 
         pw.println()
@@ -122,7 +129,7 @@
         Trace.endSection()
     }
 
-    private fun dumpParameterized(pw: PrintWriter, args: ParsedArgs) {
+    private fun dumpParameterized(fd: FileDescriptor, pw: PrintWriter, args: ParsedArgs) {
         when (args.command) {
             "bugreport-critical" -> dumpCritical(pw, args)
             "bugreport-normal" -> dumpNormal(pw, args)
@@ -130,7 +137,13 @@
             "buffers" -> dumpBuffers(pw, args)
             "config" -> dumpConfig(pw)
             "help" -> dumpHelp(pw)
-            else -> dumpTargets(args.nonFlagArgs, pw, args)
+            else -> {
+                if (args.proto) {
+                    dumpProtoTargets(args.nonFlagArgs, fd, args)
+                } else {
+                    dumpTargets(args.nonFlagArgs, pw, args)
+                }
+            }
         }
     }
 
@@ -160,6 +173,26 @@
         }
     }
 
+    private fun dumpProtoTargets(
+            targets: List<String>,
+            fd: FileDescriptor,
+            args: ParsedArgs
+    ) {
+        val systemUIProto = SystemUIProtoDump()
+        if (targets.isNotEmpty()) {
+            for (target in targets) {
+                dumpManager.dumpProtoTarget(target, systemUIProto, args.rawArgs)
+            }
+        } else {
+            dumpManager.dumpProtoDumpables(systemUIProto, args.rawArgs)
+        }
+        val buffer = BufferedOutputStream(FileOutputStream(fd))
+        buffer.use {
+            it.write(MessageNano.toByteArray(systemUIProto))
+            it.flush()
+        }
+    }
+
     private fun dumpTargets(
         targets: List<String>,
         pw: PrintWriter,
@@ -235,6 +268,7 @@
         pw.println("$ <invocation> buffers")
         pw.println("$ <invocation> bugreport-critical")
         pw.println("$ <invocation> bugreport-normal")
+        pw.println("$ <invocation> config")
         pw.println()
 
         pw.println("Targets can be listed:")
@@ -266,6 +300,7 @@
                             }
                         }
                     }
+                    PROTO -> pArgs.proto = true
                     "-t", "--tail" -> {
                         pArgs.tailLength = readArgument(iterator, arg) {
                             it.toInt()
@@ -277,6 +312,9 @@
                     "-h", "--help" -> {
                         pArgs.command = "help"
                     }
+                    // This flag is passed as part of the proto dump in Bug reports, we can ignore
+                    // it because this is our default behavior.
+                    "-a" -> {}
                     else -> {
                         throw ArgParseException("Unknown flag: $arg")
                     }
@@ -313,13 +351,21 @@
         const val PRIORITY_ARG_CRITICAL = "CRITICAL"
         const val PRIORITY_ARG_HIGH = "HIGH"
         const val PRIORITY_ARG_NORMAL = "NORMAL"
+        const val PROTO = "--proto"
     }
 }
 
 private val PRIORITY_OPTIONS =
         arrayOf(PRIORITY_ARG_CRITICAL, PRIORITY_ARG_HIGH, PRIORITY_ARG_NORMAL)
 
-private val COMMANDS = arrayOf("bugreport-critical", "bugreport-normal", "buffers", "dumpables")
+private val COMMANDS = arrayOf(
+        "bugreport-critical",
+        "bugreport-normal",
+        "buffers",
+        "dumpables",
+        "config",
+        "help"
+)
 
 private class ParsedArgs(
     val rawArgs: Array<String>,
@@ -329,6 +375,7 @@
     var tailLength: Int = 0
     var command: String? = null
     var listOnly = false
+    var proto = false
 }
 
 class ArgParseException(message: String) : Exception(message)
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index cca04da..ae78089 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -18,7 +18,9 @@
 
 import android.util.ArrayMap
 import com.android.systemui.Dumpable
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.ProtoDumpable
+import com.android.systemui.dump.nano.SystemUIProtoDump
+import com.android.systemui.plugins.log.LogBuffer
 import java.io.PrintWriter
 import javax.inject.Inject
 import javax.inject.Singleton
@@ -90,7 +92,7 @@
         target: String,
         pw: PrintWriter,
         args: Array<String>,
-        tailLength: Int
+        tailLength: Int,
     ) {
         for (dumpable in dumpables.values) {
             if (dumpable.name.endsWith(target)) {
@@ -107,6 +109,36 @@
         }
     }
 
+    @Synchronized
+    fun dumpProtoTarget(
+        target: String,
+        protoDump: SystemUIProtoDump,
+        args: Array<String>
+    ) {
+        for (dumpable in dumpables.values) {
+            if (dumpable.dumpable is ProtoDumpable && dumpable.name.endsWith(target)) {
+                dumpProtoDumpable(dumpable.dumpable, protoDump, args)
+                return
+            }
+        }
+    }
+
+    @Synchronized
+    fun dumpProtoDumpables(
+        systemUIProtoDump: SystemUIProtoDump,
+        args: Array<String>
+    ) {
+        for (dumpable in dumpables.values) {
+            if (dumpable.dumpable is ProtoDumpable) {
+                dumpProtoDumpable(
+                    dumpable.dumpable,
+                    systemUIProtoDump,
+                    args
+                )
+            }
+        }
+    }
+
     /**
      * Dumps all registered dumpables to [pw]
      */
@@ -184,6 +216,14 @@
         buffer.dumpable.dump(pw, tailLength)
     }
 
+    private fun dumpProtoDumpable(
+        protoDumpable: ProtoDumpable,
+        systemUIProtoDump: SystemUIProtoDump,
+        args: Array<String>
+    ) {
+        protoDumpable.dumpProto(systemUIProtoDump, args)
+    }
+
     private fun canAssignToNameLocked(name: String, newDumpable: Any): Boolean {
         val existingDumpable = dumpables[name]?.dumpable ?: buffers[name]?.dumpable
         return existingDumpable == null || newDumpable == existingDumpable
@@ -195,4 +235,4 @@
     val dumpable: T
 )
 
-private const val TAG = "DumpManager"
\ No newline at end of file
+private const val TAG = "DumpManager"
diff --git a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
index 0eab1af..8299b13 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
@@ -19,7 +19,7 @@
 import android.content.Context
 import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.util.io.Files
 import com.android.systemui.util.time.SystemClock
 import java.io.IOException
diff --git a/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java b/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java
index 0a41a56..da983ab 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java
+++ b/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java
@@ -51,6 +51,7 @@
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         // Simulate the NORMAL priority arg being passed to us
         mDumpHandler.dump(
+                fd,
                 pw,
                 new String[] { DumpHandler.PRIORITY_ARG, DumpHandler.PRIORITY_ARG_NORMAL });
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dump/sysui.proto b/packages/SystemUI/src/com/android/systemui/dump/sysui.proto
new file mode 100644
index 0000000..cd8c08a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dump/sysui.proto
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+syntax = "proto3";
+
+package com.android.systemui.dump;
+
+import "frameworks/base/packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto";
+
+option java_multiple_files = true;
+
+message SystemUIProtoDump {
+  repeated com.android.systemui.qs.QsTileState tiles = 1;
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
deleted file mode 100644
index 72e0ec1..0000000
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.flags;
-
-import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER;
-
-import com.android.internal.annotations.Keep;
-import com.android.systemui.R;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * List of {@link Flag} objects for use in SystemUI.
- *
- * Flag Ids are integers.
- * Ids must be unique. This is enforced in a unit test.
- * Ids need not be sequential. Flags can "claim" a chunk of ids for flags in related features with
- * a comment. This is purely for organizational purposes.
- *
- * On public release builds, flags will always return their default value. There is no way to
- * change their value on release builds.
- *
- * See {@link FeatureFlagsDebug} for instructions on flipping the flags via adb.
- */
-public class Flags {
-    public static final UnreleasedFlag TEAMFOOD = new UnreleasedFlag(1);
-
-    /***************************************/
-    // 100 - notification
-    public static final UnreleasedFlag NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
-            new UnreleasedFlag(103);
-
-    public static final UnreleasedFlag NSSL_DEBUG_LINES =
-            new UnreleasedFlag(105);
-
-    public static final UnreleasedFlag NSSL_DEBUG_REMOVE_ANIMATION =
-            new UnreleasedFlag(106);
-
-    public static final UnreleasedFlag NEW_PIPELINE_CRASH_ON_CALL_TO_OLD_PIPELINE =
-            new UnreleasedFlag(107);
-
-    public static final ResourceBooleanFlag NOTIFICATION_DRAG_TO_CONTENTS =
-            new ResourceBooleanFlag(108, R.bool.config_notificationToContents);
-
-    public static final ReleasedFlag REMOVE_UNRANKED_NOTIFICATIONS =
-            new ReleasedFlag(109);
-
-    public static final UnreleasedFlag FSI_REQUIRES_KEYGUARD =
-            new UnreleasedFlag(110, true);
-
-    public static final UnreleasedFlag INSTANT_VOICE_REPLY = new UnreleasedFlag(111, true);
-
-    public static final UnreleasedFlag NOTIFICATION_MEMORY_MONITOR_ENABLED = new UnreleasedFlag(112,
-            false);
-
-    public static final UnreleasedFlag NOTIFICATION_DISMISSAL_FADE = new UnreleasedFlag(113, true);
-
-    // next id: 114
-
-    /***************************************/
-    // 200 - keyguard/lockscreen
-
-    // ** Flag retired **
-    // public static final BooleanFlag KEYGUARD_LAYOUT =
-    //         new BooleanFlag(200, true);
-
-    public static final ReleasedFlag LOCKSCREEN_ANIMATIONS =
-            new ReleasedFlag(201);
-
-    public static final ReleasedFlag NEW_UNLOCK_SWIPE_ANIMATION =
-            new ReleasedFlag(202);
-
-    public static final ResourceBooleanFlag CHARGING_RIPPLE =
-            new ResourceBooleanFlag(203, R.bool.flag_charging_ripple);
-
-    public static final ResourceBooleanFlag BOUNCER_USER_SWITCHER =
-            new ResourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher);
-
-    public static final ResourceBooleanFlag FACE_SCANNING_ANIM =
-            new ResourceBooleanFlag(205, R.bool.config_enableFaceScanningAnimation);
-
-    public static final UnreleasedFlag LOCKSCREEN_CUSTOM_CLOCKS = new UnreleasedFlag(207);
-
-    /**
-     * Flag to enable the usage of the new bouncer data source. This is a refactor of and
-     * eventual replacement of KeyguardBouncer.java.
-     */
-    public static final UnreleasedFlag MODERN_BOUNCER = new UnreleasedFlag(208);
-
-    /**
-     * Whether the user interactor and repository should use `UserSwitcherController`.
-     *
-     * <p>If this is {@code false}, the interactor and repo skip the controller and directly access
-     * the framework APIs.
-     */
-    public static final UnreleasedFlag USER_INTERACTOR_AND_REPO_USE_CONTROLLER =
-            new UnreleasedFlag(210);
-
-    /**
-     * Whether `UserSwitcherController` should use the user interactor.
-     *
-     * <p>When this is {@code true}, the controller does not directly access framework APIs.
-     * Instead, it goes through the interactor.
-     *
-     * <p>Note: do not set this to true if {@link #USER_INTERACTOR_AND_REPO_USE_CONTROLLER} is
-     * {@code true} as it would created a cycle between controller -> interactor -> controller.
-     */
-    public static final ReleasedFlag USER_CONTROLLER_USES_INTERACTOR = new ReleasedFlag(211);
-
-    /***************************************/
-    // 300 - power menu
-    public static final ReleasedFlag POWER_MENU_LITE =
-            new ReleasedFlag(300);
-
-    /***************************************/
-    // 400 - smartspace
-    public static final ReleasedFlag SMARTSPACE_DEDUPING =
-            new ReleasedFlag(400);
-
-    public static final ReleasedFlag SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED =
-            new ReleasedFlag(401);
-
-    public static final ResourceBooleanFlag SMARTSPACE =
-            new ResourceBooleanFlag(402, R.bool.flag_smartspace);
-
-    /***************************************/
-    // 500 - quick settings
-    /**
-     * @deprecated Not needed anymore
-     */
-    @Deprecated
-    public static final ReleasedFlag NEW_USER_SWITCHER =
-            new ReleasedFlag(500);
-
-    public static final UnreleasedFlag COMBINED_QS_HEADERS =
-            new UnreleasedFlag(501, true);
-
-    public static final ResourceBooleanFlag PEOPLE_TILE =
-            new ResourceBooleanFlag(502, R.bool.flag_conversations);
-
-    public static final ResourceBooleanFlag QS_USER_DETAIL_SHORTCUT =
-            new ResourceBooleanFlag(503, R.bool.flag_lockscreen_qs_user_detail_shortcut);
-
-    /**
-     * @deprecated Not needed anymore
-     */
-    @Deprecated
-    public static final ReleasedFlag NEW_FOOTER = new ReleasedFlag(504);
-
-    public static final UnreleasedFlag NEW_HEADER = new UnreleasedFlag(505, true);
-    public static final ResourceBooleanFlag FULL_SCREEN_USER_SWITCHER =
-            new ResourceBooleanFlag(506, R.bool.config_enableFullscreenUserSwitcher);
-
-    public static final ReleasedFlag NEW_FOOTER_ACTIONS = new ReleasedFlag(507);
-
-    /***************************************/
-    // 600- status bar
-    public static final ResourceBooleanFlag STATUS_BAR_USER_SWITCHER =
-            new ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip);
-
-    public static final ReleasedFlag STATUS_BAR_LETTERBOX_APPEARANCE =
-            new ReleasedFlag(603, false);
-
-    public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE_BACKEND =
-            new UnreleasedFlag(604, false);
-
-    public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE_FRONTEND =
-            new UnreleasedFlag(605, false);
-
-    /***************************************/
-    // 700 - dialer/calls
-    public static final ReleasedFlag ONGOING_CALL_STATUS_BAR_CHIP =
-            new ReleasedFlag(700);
-
-    public static final ReleasedFlag ONGOING_CALL_IN_IMMERSIVE =
-            new ReleasedFlag(701);
-
-    public static final ReleasedFlag ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP =
-            new ReleasedFlag(702);
-
-    /***************************************/
-    // 800 - general visual/theme
-    public static final ResourceBooleanFlag MONET =
-            new ResourceBooleanFlag(800, R.bool.flag_monet);
-
-    /***************************************/
-    // 801 - region sampling
-    public static final UnreleasedFlag REGION_SAMPLING = new UnreleasedFlag(801);
-
-    // 802 - wallpaper rendering
-    public static final UnreleasedFlag USE_CANVAS_RENDERER = new UnreleasedFlag(802, true);
-
-    // 803 - screen contents translation
-    public static final UnreleasedFlag SCREEN_CONTENTS_TRANSLATION = new UnreleasedFlag(803);
-
-    /***************************************/
-    // 900 - media
-    public static final ReleasedFlag MEDIA_TAP_TO_TRANSFER = new ReleasedFlag(900);
-    public static final UnreleasedFlag MEDIA_SESSION_ACTIONS = new UnreleasedFlag(901);
-    public static final ReleasedFlag MEDIA_NEARBY_DEVICES = new ReleasedFlag(903);
-    public static final ReleasedFlag MEDIA_MUTE_AWAIT = new ReleasedFlag(904);
-    public static final UnreleasedFlag DREAM_MEDIA_COMPLICATION = new UnreleasedFlag(905);
-    public static final UnreleasedFlag DREAM_MEDIA_TAP_TO_OPEN = new UnreleasedFlag(906);
-
-    // 1000 - dock
-    public static final ReleasedFlag SIMULATE_DOCK_THROUGH_CHARGING =
-            new ReleasedFlag(1000);
-    public static final ReleasedFlag DOCK_SETUP_ENABLED = new ReleasedFlag(1001);
-
-    public static final UnreleasedFlag ROUNDED_BOX_RIPPLE =
-            new UnreleasedFlag(1002, /* teamfood= */ true);
-
-    // 1100 - windowing
-    @Keep
-    public static final SysPropBooleanFlag WM_ENABLE_SHELL_TRANSITIONS =
-            new SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false);
-
-    /**
-     * b/170163464: animate bubbles expanded view collapse with home gesture
-     */
-    @Keep
-    public static final SysPropBooleanFlag BUBBLES_HOME_GESTURE =
-            new SysPropBooleanFlag(1101, "persist.wm.debug.bubbles_home_gesture", true);
-
-    @Keep
-    public static final DeviceConfigBooleanFlag WM_ENABLE_PARTIAL_SCREEN_SHARING =
-            new DeviceConfigBooleanFlag(1102, "record_task_content",
-                    NAMESPACE_WINDOW_MANAGER, false, true);
-
-    @Keep
-    public static final SysPropBooleanFlag HIDE_NAVBAR_WINDOW =
-            new SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false);
-
-    @Keep
-    public static final SysPropBooleanFlag WM_DESKTOP_WINDOWING =
-            new SysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", false);
-
-    @Keep
-    public static final SysPropBooleanFlag WM_CAPTION_ON_SHELL =
-            new SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false);
-
-    @Keep
-    public static final SysPropBooleanFlag FLOATING_TASKS_ENABLED =
-            new SysPropBooleanFlag(1106, "persist.wm.debug.floating_tasks", false);
-
-    @Keep
-    public static final SysPropBooleanFlag SHOW_FLOATING_TASKS_AS_BUBBLES =
-            new SysPropBooleanFlag(1107, "persist.wm.debug.floating_tasks_as_bubbles", false);
-
-    @Keep
-    public static final SysPropBooleanFlag ENABLE_FLING_TO_DISMISS_BUBBLE =
-            new SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true);
-    @Keep
-    public static final SysPropBooleanFlag ENABLE_FLING_TO_DISMISS_PIP =
-            new SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true);
-
-    @Keep
-    public static final SysPropBooleanFlag ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
-            new SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false);
-
-    // 1200 - predictive back
-    @Keep
-    public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK = new SysPropBooleanFlag(
-            1200, "persist.wm.debug.predictive_back", true);
-    @Keep
-    public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK_ANIM = new SysPropBooleanFlag(
-            1201, "persist.wm.debug.predictive_back_anim", false);
-    @Keep
-    public static final SysPropBooleanFlag WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
-            new SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false);
-
-    public static final UnreleasedFlag NEW_BACK_AFFORDANCE =
-            new UnreleasedFlag(1203, false /* teamfood */);
-
-    // 1300 - screenshots
-
-    public static final UnreleasedFlag SCREENSHOT_REQUEST_PROCESSOR = new UnreleasedFlag(1300);
-    public static final UnreleasedFlag SCREENSHOT_WORK_PROFILE_POLICY = new UnreleasedFlag(1301);
-
-    // 1400 - columbus
-    public static final ReleasedFlag QUICK_TAP_IN_PCC = new ReleasedFlag(1400);
-
-    // 1500 - chooser
-    public static final UnreleasedFlag CHOOSER_UNBUNDLED = new UnreleasedFlag(1500);
-
-    // Pay no attention to the reflection behind the curtain.
-    // ========================== Curtain ==========================
-    // |                                                           |
-    // |  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  |
-    private static Map<Integer, Flag<?>> sFlagMap;
-    static Map<Integer, Flag<?>> collectFlags() {
-        if (sFlagMap != null) {
-            return sFlagMap;
-        }
-
-        Map<Integer, Flag<?>> flags = new HashMap<>();
-        List<Field> flagFields = getFlagFields();
-
-        for (Field field : flagFields) {
-            try {
-                Flag<?> flag = (Flag<?>) field.get(null);
-                flags.put(flag.getId(), flag);
-            } catch (IllegalAccessException e) {
-                // no-op
-            }
-        }
-
-        sFlagMap = flags;
-
-        return sFlagMap;
-    }
-
-    static List<Field> getFlagFields() {
-        Field[] fields = Flags.class.getFields();
-        List<Field> result = new ArrayList<>();
-
-        for (Field field : fields) {
-            Class<?> t = field.getType();
-            if (Flag.class.isAssignableFrom(t)) {
-                result.add(field);
-            }
-        }
-
-        return result;
-    }
-    // |  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  |
-    // |                                                           |
-    // \_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
new file mode 100644
index 0000000..9ef3f5d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.flags
+
+import android.provider.DeviceConfig
+import com.android.internal.annotations.Keep
+import com.android.systemui.R
+import java.lang.reflect.Field
+
+/**
+ * List of [Flag] objects for use in SystemUI.
+ *
+ * Flag Ids are integers. Ids must be unique. This is enforced in a unit test. Ids need not be
+ * sequential. Flags can "claim" a chunk of ids for flags in related features with a comment. This
+ * is purely for organizational purposes.
+ *
+ * On public release builds, flags will always return their default value. There is no way to change
+ * their value on release builds.
+ *
+ * See [FeatureFlagsDebug] for instructions on flipping the flags via adb.
+ */
+object Flags {
+    @JvmField val TEAMFOOD = UnreleasedFlag(1)
+
+    // 100 - notification
+    // TODO(b/254512751): Tracking Bug
+    val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING = UnreleasedFlag(103)
+
+    // TODO(b/254512732): Tracking Bug
+    @JvmField val NSSL_DEBUG_LINES = UnreleasedFlag(105)
+
+    // TODO(b/254512505): Tracking Bug
+    @JvmField val NSSL_DEBUG_REMOVE_ANIMATION = UnreleasedFlag(106)
+
+    // TODO(b/254512624): Tracking Bug
+    @JvmField
+    val NOTIFICATION_DRAG_TO_CONTENTS =
+        ResourceBooleanFlag(108, R.bool.config_notificationToContents)
+
+    // TODO(b/254512517): Tracking Bug
+    val FSI_REQUIRES_KEYGUARD = UnreleasedFlag(110, teamfood = true)
+
+    // TODO(b/254512538): Tracking Bug
+    val INSTANT_VOICE_REPLY = UnreleasedFlag(111, teamfood = true)
+
+    // TODO(b/254512425): Tracking Bug
+    val NOTIFICATION_MEMORY_MONITOR_ENABLED = UnreleasedFlag(112, teamfood = false)
+
+    // TODO(b/254512731): Tracking Bug
+    @JvmField val NOTIFICATION_DISMISSAL_FADE = UnreleasedFlag(113, teamfood = true)
+    val STABILITY_INDEX_FIX = UnreleasedFlag(114, teamfood = true)
+    val SEMI_STABLE_SORT = UnreleasedFlag(115, teamfood = true)
+    @JvmField val NOTIFICATION_GROUP_CORNER = UnreleasedFlag(116, true)
+    // next id: 117
+
+    // 200 - keyguard/lockscreen
+    // ** Flag retired **
+    // public static final BooleanFlag KEYGUARD_LAYOUT =
+    //         new BooleanFlag(200, true);
+    // TODO(b/254512713): Tracking Bug
+    @JvmField val LOCKSCREEN_ANIMATIONS = ReleasedFlag(201)
+
+    // TODO(b/254512750): Tracking Bug
+    val NEW_UNLOCK_SWIPE_ANIMATION = ReleasedFlag(202)
+    val CHARGING_RIPPLE = ResourceBooleanFlag(203, R.bool.flag_charging_ripple)
+
+    // TODO(b/254512281): Tracking Bug
+    @JvmField
+    val BOUNCER_USER_SWITCHER = ResourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher)
+
+    // TODO(b/254512676): Tracking Bug
+    @JvmField val LOCKSCREEN_CUSTOM_CLOCKS = UnreleasedFlag(207, teamfood = true)
+
+    /**
+     * Flag to enable the usage of the new bouncer data source. This is a refactor of and eventual
+     * replacement of KeyguardBouncer.java.
+     */
+    // TODO(b/254512385): Tracking Bug
+    @JvmField val MODERN_BOUNCER = UnreleasedFlag(208)
+
+    /**
+     * Whether the user interactor and repository should use `UserSwitcherController`.
+     *
+     * If this is `false`, the interactor and repo skip the controller and directly access the
+     * framework APIs.
+     */
+    // TODO(b/254513286): Tracking Bug
+    val USER_INTERACTOR_AND_REPO_USE_CONTROLLER = UnreleasedFlag(210)
+
+    /**
+     * Whether `UserSwitcherController` should use the user interactor.
+     *
+     * When this is `true`, the controller does not directly access framework APIs. Instead, it goes
+     * through the interactor.
+     *
+     * Note: do not set this to true if [.USER_INTERACTOR_AND_REPO_USE_CONTROLLER] is `true` as it
+     * would created a cycle between controller -> interactor -> controller.
+     */
+    // TODO(b/254513102): Tracking Bug
+    val USER_CONTROLLER_USES_INTERACTOR = ReleasedFlag(211)
+
+    /**
+     * Whether the clock on a wide lock screen should use the new "stepping" animation for moving
+     * the digits when the clock moves.
+     */
+    @JvmField val STEP_CLOCK_ANIMATION = UnreleasedFlag(212)
+
+    // 300 - power menu
+    // TODO(b/254512600): Tracking Bug
+    @JvmField val POWER_MENU_LITE = ReleasedFlag(300)
+
+    // 400 - smartspace
+
+    // TODO(b/254513100): Tracking Bug
+    val SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED = ReleasedFlag(401)
+    val SMARTSPACE = ResourceBooleanFlag(402, R.bool.flag_smartspace)
+
+    // 500 - quick settings
+    @Deprecated("Not needed anymore") val NEW_USER_SWITCHER = ReleasedFlag(500)
+
+    // TODO(b/254512321): Tracking Bug
+    @JvmField val COMBINED_QS_HEADERS = UnreleasedFlag(501, teamfood = true)
+    val PEOPLE_TILE = ResourceBooleanFlag(502, R.bool.flag_conversations)
+    @JvmField
+    val QS_USER_DETAIL_SHORTCUT =
+        ResourceBooleanFlag(503, R.bool.flag_lockscreen_qs_user_detail_shortcut)
+
+    // TODO(b/254512699): Tracking Bug
+    @Deprecated("Not needed anymore") val NEW_FOOTER = ReleasedFlag(504)
+
+    // TODO(b/254512747): Tracking Bug
+    val NEW_HEADER = UnreleasedFlag(505, teamfood = true)
+
+    // TODO(b/254512383): Tracking Bug
+    @JvmField
+    val FULL_SCREEN_USER_SWITCHER =
+        ResourceBooleanFlag(506, R.bool.config_enableFullscreenUserSwitcher)
+
+    // TODO(b/254512678): Tracking Bug
+    @JvmField val NEW_FOOTER_ACTIONS = ReleasedFlag(507)
+
+    // 600- status bar
+    // TODO(b/254513246): Tracking Bug
+    val STATUS_BAR_USER_SWITCHER = ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip)
+
+    // TODO(b/254513025): Tracking Bug
+    val STATUS_BAR_LETTERBOX_APPEARANCE = ReleasedFlag(603, teamfood = false)
+
+    // TODO(b/254512623): Tracking Bug
+    @Deprecated("Replaced by mobile and wifi specific flags.")
+    val NEW_STATUS_BAR_PIPELINE_BACKEND = UnreleasedFlag(604, teamfood = false)
+
+    // TODO(b/254512660): Tracking Bug
+    @Deprecated("Replaced by mobile and wifi specific flags.")
+    val NEW_STATUS_BAR_PIPELINE_FRONTEND = UnreleasedFlag(605, teamfood = false)
+
+    val NEW_STATUS_BAR_MOBILE_ICONS = UnreleasedFlag(606, false)
+
+    val NEW_STATUS_BAR_WIFI_ICON = UnreleasedFlag(607, false)
+
+    // 700 - dialer/calls
+    // TODO(b/254512734): Tracking Bug
+    val ONGOING_CALL_STATUS_BAR_CHIP = ReleasedFlag(700)
+
+    // TODO(b/254512681): Tracking Bug
+    val ONGOING_CALL_IN_IMMERSIVE = ReleasedFlag(701)
+
+    // TODO(b/254512753): Tracking Bug
+    val ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP = ReleasedFlag(702)
+
+    // 800 - general visual/theme
+    @JvmField val MONET = ResourceBooleanFlag(800, R.bool.flag_monet)
+
+    // 801 - region sampling
+    // TODO(b/254512848): Tracking Bug
+    val REGION_SAMPLING = UnreleasedFlag(801)
+
+    // 802 - wallpaper rendering
+    // TODO(b/254512923): Tracking Bug
+    @JvmField val USE_CANVAS_RENDERER = ReleasedFlag(802)
+
+    // 803 - screen contents translation
+    // TODO(b/254513187): Tracking Bug
+    val SCREEN_CONTENTS_TRANSLATION = UnreleasedFlag(803)
+
+    // 804 - monochromatic themes
+    @JvmField val MONOCHROMATIC_THEMES = UnreleasedFlag(804)
+
+    // 900 - media
+    // TODO(b/254512697): Tracking Bug
+    val MEDIA_TAP_TO_TRANSFER = ReleasedFlag(900)
+
+    // TODO(b/254512502): Tracking Bug
+    val MEDIA_SESSION_ACTIONS = UnreleasedFlag(901)
+
+    // TODO(b/254512726): Tracking Bug
+    val MEDIA_NEARBY_DEVICES = ReleasedFlag(903)
+
+    // TODO(b/254512695): Tracking Bug
+    val MEDIA_MUTE_AWAIT = ReleasedFlag(904)
+
+    // TODO(b/254512654): Tracking Bug
+    @JvmField val DREAM_MEDIA_COMPLICATION = UnreleasedFlag(905)
+
+    // TODO(b/254512673): Tracking Bug
+    @JvmField val DREAM_MEDIA_TAP_TO_OPEN = UnreleasedFlag(906)
+
+    // TODO(b/254513168): Tracking Bug
+    val UMO_SURFACE_RIPPLE = UnreleasedFlag(907)
+
+    // 1000 - dock
+    val SIMULATE_DOCK_THROUGH_CHARGING = ReleasedFlag(1000)
+
+    // TODO(b/254512444): Tracking Bug
+    @JvmField val DOCK_SETUP_ENABLED = ReleasedFlag(1001)
+
+    // TODO(b/254512758): Tracking Bug
+    @JvmField val ROUNDED_BOX_RIPPLE = ReleasedFlag(1002)
+
+    // TODO(b/254512525): Tracking Bug
+    @JvmField val REFACTORED_DOCK_SETUP = ReleasedFlag(1003, teamfood = true)
+
+    // 1100 - windowing
+    @Keep
+    val WM_ENABLE_SHELL_TRANSITIONS =
+        SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false)
+
+    /** b/170163464: animate bubbles expanded view collapse with home gesture */
+    @Keep
+    val BUBBLES_HOME_GESTURE =
+        SysPropBooleanFlag(1101, "persist.wm.debug.bubbles_home_gesture", true)
+
+    // TODO(b/254513207): Tracking Bug
+    @JvmField
+    @Keep
+    val WM_ENABLE_PARTIAL_SCREEN_SHARING =
+        DeviceConfigBooleanFlag(
+            1102,
+            "record_task_content",
+            DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+            false,
+            teamfood = true
+        )
+
+    // TODO(b/254512674): Tracking Bug
+    @JvmField
+    @Keep
+    val HIDE_NAVBAR_WINDOW = SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false)
+
+    @Keep
+    val WM_DESKTOP_WINDOWING = SysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", false)
+
+    @Keep
+    val WM_CAPTION_ON_SHELL = SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false)
+
+    @Keep
+    val FLOATING_TASKS_ENABLED = SysPropBooleanFlag(1106, "persist.wm.debug.floating_tasks", false)
+
+    @Keep
+    val SHOW_FLOATING_TASKS_AS_BUBBLES =
+        SysPropBooleanFlag(1107, "persist.wm.debug.floating_tasks_as_bubbles", false)
+
+    @Keep
+    val ENABLE_FLING_TO_DISMISS_BUBBLE =
+        SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true)
+
+    @Keep
+    val ENABLE_FLING_TO_DISMISS_PIP =
+        SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true)
+
+    @Keep
+    val ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
+        SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false)
+
+    // 1200 - predictive back
+    @Keep
+    val WM_ENABLE_PREDICTIVE_BACK =
+        SysPropBooleanFlag(1200, "persist.wm.debug.predictive_back", true)
+
+    @Keep
+    val WM_ENABLE_PREDICTIVE_BACK_ANIM =
+        SysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", false)
+
+    @Keep
+    val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
+        SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false)
+
+    // TODO(b/254512728): Tracking Bug
+    @JvmField val NEW_BACK_AFFORDANCE = UnreleasedFlag(1203, teamfood = false)
+
+    // 1300 - screenshots
+    // TODO(b/254512719): Tracking Bug
+    @JvmField val SCREENSHOT_REQUEST_PROCESSOR = UnreleasedFlag(1300)
+
+    // TODO(b/254513155): Tracking Bug
+    @JvmField val SCREENSHOT_WORK_PROFILE_POLICY = UnreleasedFlag(1301)
+
+    // 1400 - columbus
+    // TODO(b/254512756): Tracking Bug
+    val QUICK_TAP_IN_PCC = ReleasedFlag(1400)
+
+    // 1500 - chooser
+    // TODO(b/254512507): Tracking Bug
+    val CHOOSER_UNBUNDLED = UnreleasedFlag(1500)
+
+    // 1700 - clipboard
+    @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700)
+
+    // 1800 - shade container
+    @JvmField val LEAVE_SHADE_OPEN_FOR_BUGREPORT = UnreleasedFlag(1800, true)
+
+    // Pay no attention to the reflection behind the curtain.
+    // ========================== Curtain ==========================
+    // |                                                           |
+    // |  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  |
+    @JvmStatic
+    fun collectFlags(): Map<Int, Flag<*>> {
+        return flagFields
+            .map { field ->
+                // field[null] returns the current value of the field.
+                // See java.lang.Field#get
+                val flag = field[null] as Flag<*>
+                flag.id to flag
+            }
+            .toMap()
+    }
+
+    // |  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  |
+    @JvmStatic
+    val flagFields: List<Field>
+        get() {
+            return Flags::class.java.fields.filter { f ->
+                Flag::class.java.isAssignableFrom(f.type)
+            }
+        }
+    // |                                                           |
+    // \_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index da5819a..3ef5499 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -116,6 +116,7 @@
 import com.android.systemui.MultiListLayout.MultiListAdapter;
 import com.android.systemui.animation.DialogCuj;
 import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.animation.Expandable;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -448,10 +449,11 @@
      *
      * @param keyguardShowing     True if keyguard is showing
      * @param isDeviceProvisioned True if device is provisioned
-     * @param view                The view from which we should animate the dialog when showing it
+     * @param expandable          The expandable from which we should animate the dialog when
+     *                            showing it
      */
     public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned,
-            @Nullable View view) {
+            @Nullable Expandable expandable) {
         mKeyguardShowing = keyguardShowing;
         mDeviceProvisioned = isDeviceProvisioned;
         if (mDialog != null && mDialog.isShowing()) {
@@ -463,7 +465,7 @@
             mDialog.dismiss();
             mDialog = null;
         } else {
-            handleShow(view);
+            handleShow(expandable);
         }
     }
 
@@ -495,7 +497,7 @@
         }
     }
 
-    protected void handleShow(@Nullable View view) {
+    protected void handleShow(@Nullable Expandable expandable) {
         awakenIfNecessary();
         mDialog = createDialog();
         prepareDialog();
@@ -507,10 +509,12 @@
         // Don't acquire soft keyboard focus, to avoid destroying state when capturing bugreports
         mDialog.getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
 
-        if (view != null) {
-            mDialogLaunchAnimator.showFromView(mDialog, view,
-                    new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
-                            INTERACTION_JANK_TAG));
+        DialogLaunchAnimator.Controller controller =
+                expandable != null ? expandable.dialogLaunchController(
+                        new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+                                INTERACTION_JANK_TAG)) : null;
+        if (controller != null) {
+            mDialogLaunchAnimator.show(mDialog, controller);
         } else {
             mDialog.show();
         }
@@ -1016,8 +1020,9 @@
                             Log.w(TAG, "Bugreport handler could not be launched");
                             mIActivityManager.requestInteractiveBugReport();
                         }
-                        // Close shade so user sees the activity
-                        mCentralSurfacesOptional.ifPresent(CentralSurfaces::collapseShade);
+                        // Maybe close shade (depends on a flag) so user sees the activity
+                        mCentralSurfacesOptional.ifPresent(
+                                CentralSurfaces::collapseShadeForBugreport);
                     } catch (RemoteException e) {
                     }
                 }
@@ -1036,8 +1041,8 @@
                 mMetricsLogger.action(MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
                 mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS);
                 mIActivityManager.requestFullBugReport();
-                // Close shade so user sees the activity
-                mCentralSurfacesOptional.ifPresent(CentralSurfaces::collapseShade);
+                // Maybe close shade (depends on a flag) so user sees the activity
+                mCentralSurfacesOptional.ifPresent(CentralSurfaces::collapseShadeForBugreport);
             } catch (RemoteException e) {
             }
             return false;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 9b036c1..c8370b4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -401,6 +401,11 @@
     private final float mWindowCornerRadius;
 
     /**
+     * The duration in milliseconds of the dream open animation.
+     */
+    private final int mDreamOpenAnimationDuration;
+
+    /**
      * The animation used for hiding keyguard. This is used to fetch the animation timings if
      * WindowManager is not providing us with them.
      */
@@ -794,6 +799,8 @@
             KeyguardUpdateMonitor.StrongAuthTracker strongAuthTracker =
                     mUpdateMonitor.getStrongAuthTracker();
             int strongAuth = strongAuthTracker.getStrongAuthForUser(currentUser);
+            boolean allowedNonStrongAfterIdleTimeout =
+                    strongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout(currentUser);
 
             if (any && !strongAuthTracker.hasUserAuthenticatedSinceBoot()) {
                 return KeyguardSecurityView.PROMPT_REASON_RESTART;
@@ -812,6 +819,8 @@
             } else if (any && (strongAuth
                     & STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT) != 0) {
                 return KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT;
+            } else if (any && !allowedNonStrongAfterIdleTimeout) {
+                return KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT;
             }
             return KeyguardSecurityView.PROMPT_REASON_NONE;
         }
@@ -942,8 +951,7 @@
                         }
 
                         mOccludeByDreamAnimator = ValueAnimator.ofFloat(0f, 1f);
-                        // Use the same duration as for the UNOCCLUDE.
-                        mOccludeByDreamAnimator.setDuration(UNOCCLUDE_ANIMATION_DURATION);
+                        mOccludeByDreamAnimator.setDuration(mDreamOpenAnimationDuration);
                         mOccludeByDreamAnimator.setInterpolator(Interpolators.LINEAR);
                         mOccludeByDreamAnimator.addUpdateListener(
                                 animation -> {
@@ -1175,6 +1183,9 @@
         mPowerButtonY = context.getResources().getDimensionPixelSize(
                 R.dimen.physical_power_button_center_screen_location_y);
         mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+
+        mDreamOpenAnimationDuration = context.getResources().getInteger(
+                com.android.internal.R.integer.config_dreamOpenAnimationDuration);
     }
 
     public void userActivity() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 56f1ac4..56a1f1a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -43,6 +43,7 @@
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
+import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -72,6 +73,7 @@
             FalsingModule.class,
             KeyguardQuickAffordanceModule.class,
             KeyguardRepositoryModule.class,
+            StartKeyguardTransitionModule.class,
         })
 public class KeyguardModule {
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 45b668e..b186ae0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.common.shared.model.Position
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.doze.DozeHost
+import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import javax.inject.Inject
@@ -85,6 +86,9 @@
      */
     val dozeAmount: Flow<Float>
 
+    /** Observable for the [StatusBarState] */
+    val statusBarState: Flow<StatusBarState>
+
     /**
      * Returns `true` if the keyguard is showing; `false` otherwise.
      *
@@ -185,6 +189,24 @@
         return keyguardStateController.isShowing
     }
 
+    override val statusBarState: Flow<StatusBarState> = conflatedCallbackFlow {
+        val callback =
+            object : StatusBarStateController.StateListener {
+                override fun onStateChanged(state: Int) {
+                    trySendWithFailureLogging(statusBarStateIntToObject(state), TAG, "state")
+                }
+            }
+
+        statusBarStateController.addCallback(callback)
+        trySendWithFailureLogging(
+            statusBarStateIntToObject(statusBarStateController.getState()),
+            TAG,
+            "initial state"
+        )
+
+        awaitClose { statusBarStateController.removeCallback(callback) }
+    }
+
     override fun setAnimateDozingTransitions(animate: Boolean) {
         _animateBottomAreaDozingTransitions.value = animate
     }
@@ -197,6 +219,15 @@
         _clockPosition.value = Position(x, y)
     }
 
+    private fun statusBarStateIntToObject(value: Int): StatusBarState {
+        return when (value) {
+            0 -> StatusBarState.SHADE
+            1 -> StatusBarState.KEYGUARD
+            2 -> StatusBarState.SHADE_LOCKED
+            else -> throw IllegalArgumentException("Invalid StatusBarState value: $value")
+        }
+    }
+
     companion object {
         private const val TAG = "KeyguardRepositoryImpl"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
new file mode 100644
index 0000000..e8532ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -0,0 +1,169 @@
+/*
+ * 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.keyguard.data.repository
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.animation.ValueAnimator.AnimatorUpdateListener
+import android.annotation.FloatRange
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filter
+
+@SysUISingleton
+class KeyguardTransitionRepository @Inject constructor() {
+    /*
+     * Each transition between [KeyguardState]s will have an associated Flow.
+     * In order to collect these events, clients should call [transition].
+     */
+    private val _transitions = MutableStateFlow(TransitionStep())
+    val transitions = _transitions.asStateFlow()
+
+    /* Information about the active transition. */
+    private var currentTransitionInfo: TransitionInfo? = null
+    /*
+     * When manual control of the transition is requested, a unique [UUID] is used as the handle
+     * to permit calls to [updateTransition]
+     */
+    private var updateTransitionId: UUID? = null
+
+    /**
+     * Interactors that require information about changes between [KeyguardState]s will call this to
+     * register themselves for flowable [TransitionStep]s when that transition occurs.
+     */
+    fun transition(from: KeyguardState, to: KeyguardState): Flow<TransitionStep> {
+        return transitions.filter { step -> step.from == from && step.to == to }
+    }
+
+    /**
+     * Begin a transition from one state to another. The [info.from] must match
+     * [currentTransitionInfo.to], or the request will be denied. This is enforced to avoid
+     * unplanned transitions.
+     */
+    fun startTransition(info: TransitionInfo): UUID? {
+        if (currentTransitionInfo != null) {
+            // Open questions:
+            // * Queue of transitions? buffer of 1?
+            // * Are transitions cancellable if a new one is triggered?
+            // * What validation does this need to do?
+            Log.wtf(TAG, "Transition still active: $currentTransitionInfo")
+            return null
+        }
+        currentTransitionInfo?.animator?.cancel()
+
+        currentTransitionInfo = info
+        info.animator?.let { animator ->
+            // An animator was provided, so use it to run the transition
+            animator.setFloatValues(0f, 1f)
+            val updateListener =
+                object : AnimatorUpdateListener {
+                    override fun onAnimationUpdate(animation: ValueAnimator) {
+                        emitTransition(
+                            info,
+                            (animation.getAnimatedValue() as Float),
+                            TransitionState.RUNNING
+                        )
+                    }
+                }
+            val adapter =
+                object : AnimatorListenerAdapter() {
+                    override fun onAnimationStart(animation: Animator) {
+                        Log.i(TAG, "Starting transition: $info")
+                        emitTransition(info, 0f, TransitionState.STARTED)
+                    }
+                    override fun onAnimationCancel(animation: Animator) {
+                        Log.i(TAG, "Cancelling transition: $info")
+                    }
+                    override fun onAnimationEnd(animation: Animator) {
+                        Log.i(TAG, "Ending transition: $info")
+                        emitTransition(info, 1f, TransitionState.FINISHED)
+                        animator.removeListener(this)
+                        animator.removeUpdateListener(updateListener)
+                    }
+                }
+            animator.addListener(adapter)
+            animator.addUpdateListener(updateListener)
+            animator.start()
+            return@startTransition null
+        }
+            ?: run {
+                Log.i(TAG, "Starting transition (manual): $info")
+                emitTransition(info, 0f, TransitionState.STARTED)
+
+                // No animator, so it's manual. Provide a mechanism to callback
+                updateTransitionId = UUID.randomUUID()
+                return@startTransition updateTransitionId
+            }
+    }
+
+    /**
+     * Allows manual control of a transition. When calling [startTransition], the consumer must pass
+     * in a null animator. In return, it will get a unique [UUID] that will be validated to allow
+     * further updates.
+     *
+     * When the transition is over, TransitionState.FINISHED must be passed into the [state]
+     * parameter.
+     */
+    fun updateTransition(
+        transitionId: UUID,
+        @FloatRange(from = 0.0, to = 1.0) value: Float,
+        state: TransitionState
+    ) {
+        if (updateTransitionId != transitionId) {
+            Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
+            return
+        }
+
+        if (currentTransitionInfo == null) {
+            Log.wtf(TAG, "Attempting to update with null 'currentTransitionInfo'")
+            return
+        }
+
+        currentTransitionInfo?.let { info ->
+            if (state == TransitionState.FINISHED) {
+                updateTransitionId = null
+                Log.i(TAG, "Ending transition: $info")
+            }
+
+            emitTransition(info, value, state)
+        }
+    }
+
+    private fun emitTransition(
+        info: TransitionInfo,
+        value: Float,
+        transitionState: TransitionState
+    ) {
+        if (transitionState == TransitionState.FINISHED) {
+            currentTransitionInfo = null
+        }
+        _transitions.value = TransitionStep(info.from, info.to, value, transitionState)
+    }
+
+    companion object {
+        private const val TAG = "KeyguardTransitionRepository"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
new file mode 100644
index 0000000..4003766
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class AodLockscreenTransitionInteractor
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    private val keyguardRepository: KeyguardRepository,
+    private val keyguardTransitionRepository: KeyguardTransitionRepository,
+) : TransitionInteractor("AOD<->LOCKSCREEN") {
+
+    override fun start() {
+        scope.launch {
+            keyguardRepository.isDozing.collect { isDozing ->
+                if (isDozing) {
+                    keyguardTransitionRepository.startTransition(
+                        TransitionInfo(
+                            name,
+                            KeyguardState.LOCKSCREEN,
+                            KeyguardState.AOD,
+                            getAnimator(),
+                        )
+                    )
+                } else {
+                    keyguardTransitionRepository.startTransition(
+                        TransitionInfo(
+                            name,
+                            KeyguardState.AOD,
+                            KeyguardState.LOCKSCREEN,
+                            getAnimator(),
+                        )
+                    )
+                }
+            }
+        }
+    }
+
+    private fun getAnimator(): ValueAnimator {
+        return ValueAnimator().apply {
+            setInterpolator(Interpolators.LINEAR)
+            setDuration(TRANSITION_DURATION_MS)
+        }
+    }
+
+    companion object {
+        private const val TRANSITION_DURATION_MS = 500L
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
index 7d4db37..2af9318 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
@@ -273,8 +273,8 @@
     /** Tell the bouncer to start the pre hide animation. */
     fun startDisappearAnimation(runnable: Runnable) {
         val finishRunnable = Runnable {
-            repository.setStartDisappearAnimation(null)
             runnable.run()
+            repository.setStartDisappearAnimation(null)
         }
         repository.setStartDisappearAnimation(finishRunnable)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 192919e..fc2269c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -38,7 +38,7 @@
     val dozeAmount: Flow<Float> = repository.dozeAmount
     /** Whether the system is in doze mode. */
     val isDozing: Flow<Boolean> = repository.isDozing
-    /** Whether the keyguard is showing ot not. */
+    /** Whether the keyguard is showing to not. */
     val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
 
     fun isKeyguardShowing(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
new file mode 100644
index 0000000..b166681
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.util.Log
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import java.util.Set
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardTransitionCoreStartable
+@Inject
+constructor(
+    private val interactors: Set<TransitionInteractor>,
+) : CoreStartable {
+
+    override fun start() {
+        // By listing the interactors in a when, the compiler will help enforce all classes
+        // extending the sealed class [TransitionInteractor] will be initialized.
+        interactors.forEach {
+            // `when` needs to be an expression in order for the compiler to enforce it being
+            // exhaustive
+            val ret =
+                when (it) {
+                    is LockscreenBouncerTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is AodLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
+                }
+            it.start()
+        }
+    }
+
+    companion object {
+        private const val TAG = "KeyguardTransitionCoreStartable"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
new file mode 100644
index 0000000..59bb22786
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -0,0 +1,37 @@
+/*
+ *  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.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates business-logic related to the keyguard transitions. */
+@SysUISingleton
+class KeyguardTransitionInteractor
+@Inject
+constructor(
+    repository: KeyguardTransitionRepository,
+) {
+    /** AOD->LOCKSCREEN transition information. */
+    val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
new file mode 100644
index 0000000..3c2a12e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.util.kotlin.sample
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class LockscreenBouncerTransitionInteractor
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    private val keyguardRepository: KeyguardRepository,
+    private val shadeRepository: ShadeRepository,
+    private val keyguardTransitionRepository: KeyguardTransitionRepository,
+) : TransitionInteractor("LOCKSCREEN<->BOUNCER") {
+
+    private var transitionId: UUID? = null
+
+    override fun start() {
+        scope.launch {
+            shadeRepository.shadeModel.sample(
+                combine(
+                    keyguardTransitionRepository.transitions,
+                    keyguardRepository.statusBarState,
+                ) { transitions, statusBarState ->
+                    Pair(transitions, statusBarState)
+                }
+            ) { shadeModel, pair ->
+                val (transitions, statusBarState) = pair
+
+                val id = transitionId
+                if (id != null) {
+                    // An existing `id` means a transition is started, and calls to
+                    // `updateTransition` will control it until FINISHED
+                    keyguardTransitionRepository.updateTransition(
+                        id,
+                        shadeModel.expansionAmount,
+                        if (shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f) {
+                            transitionId = null
+                            TransitionState.FINISHED
+                        } else {
+                            TransitionState.RUNNING
+                        }
+                    )
+                } else {
+                    // TODO (b/251849525): Remove statusbarstate check when that state is integrated
+                    // into KeyguardTransitionRepository
+                    val isOnLockscreen =
+                        transitions.transitionState == TransitionState.FINISHED &&
+                            transitions.to == KeyguardState.LOCKSCREEN
+                    if (
+                        isOnLockscreen &&
+                            shadeModel.isUserDragging &&
+                            statusBarState != SHADE_LOCKED
+                    ) {
+                        transitionId =
+                            keyguardTransitionRepository.startTransition(
+                                TransitionInfo(
+                                    ownerName = name,
+                                    from = KeyguardState.LOCKSCREEN,
+                                    to = KeyguardState.BOUNCER,
+                                    animator = null,
+                                )
+                            )
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
new file mode 100644
index 0000000..74c542c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+abstract class StartKeyguardTransitionModule {
+
+    @Binds
+    @IntoMap
+    @ClassKey(KeyguardTransitionCoreStartable::class)
+    abstract fun bind(impl: KeyguardTransitionCoreStartable): CoreStartable
+
+    @Binds
+    @IntoSet
+    abstract fun lockscreenBouncer(
+        impl: LockscreenBouncerTransitionInteractor
+    ): TransitionInteractor
+
+    @Binds
+    @IntoSet
+    abstract fun aodLockscreen(impl: AodLockscreenTransitionInteractor): TransitionInteractor
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
new file mode 100644
index 0000000..a2a46d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.keyguard.domain.interactor
+/**
+ * Each TransitionInteractor is responsible for determining under which conditions to notify
+ * [KeyguardTransitionRepository] to signal a transition. When (and if) the transition occurs is
+ * determined by [KeyguardTransitionRepository].
+ *
+ * [name] field should be a unique identifiable string representing this state, used primarily for
+ * logging
+ *
+ * MUST list implementing classes in dagger module [StartKeyguardTransitionModule] and also in the
+ * 'when' clause of [KeyguardTransitionCoreStartable]
+ */
+sealed class TransitionInteractor(val name: String) {
+
+    abstract fun start()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
new file mode 100644
index 0000000..f66d5d3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.keyguard.shared.model
+
+/** List of all possible states to transition to/from */
+enum class KeyguardState {
+    /** For initialization only */
+    NONE,
+    /* Always-on Display. The device is in a low-power mode with a minimal UI visible */
+    AOD,
+    /*
+     * The security screen prompt UI, containing PIN, Password, Pattern, and all FPS
+     * (Fingerprint Sensor) variations, for the user to verify their credentials
+     */
+    BOUNCER,
+    /*
+     * Device is actively displaying keyguard UI and is not in low-power mode. Device may be
+     * unlocked if SWIPE security method is used, or if face lockscreen bypass is false.
+     */
+    LOCKSCREEN,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/StatusBarState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/StatusBarState.kt
new file mode 100644
index 0000000..bb95347
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/StatusBarState.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.keyguard.shared.model
+
+/** See [com.android.systemui.statusbar.StatusBarState] for definitions */
+enum class StatusBarState {
+    SHADE,
+    KEYGUARD,
+    SHADE_LOCKED,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
new file mode 100644
index 0000000..bfccf3fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.keyguard.shared.model
+
+import android.animation.ValueAnimator
+
+/** Tracks who is controlling the current transition, and how to run it. */
+data class TransitionInfo(
+    val ownerName: String,
+    val from: KeyguardState,
+    val to: KeyguardState,
+    val animator: ValueAnimator?, // 'null' animator signal manual control
+) {
+    override fun toString(): String =
+        "TransitionInfo(ownerName=$ownerName, from=$from, to=$to, " +
+            (if (animator != null) {
+                "animated"
+            } else {
+                "manual"
+            }) +
+            ")"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
new file mode 100644
index 0000000..d8691c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.keyguard.shared.model
+
+/** Possible states for a running transition between [State] */
+enum class TransitionState {
+    NONE,
+    STARTED,
+    RUNNING,
+    FINISHED
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
new file mode 100644
index 0000000..688ec91
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.keyguard.shared.model
+
+/** This information will flow from the [KeyguardTransitionRepository] to control the UI layer */
+data class TransitionStep(
+    val from: KeyguardState = KeyguardState.NONE,
+    val to: KeyguardState = KeyguardState.NONE,
+    val value: Float = 0f, // constrained [0.0, 1.0]
+    val transitionState: TransitionState = TransitionState.NONE,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
index 5651399..f9e341c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
@@ -19,6 +19,9 @@
 import android.app.ActivityManager
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogcatEchoTracker
+
 import javax.inject.Inject
 
 @SysUISingleton
@@ -26,7 +29,7 @@
     private val dumpManager: DumpManager,
     private val logcatEchoTracker: LogcatEchoTracker
 ) {
-    /* limit the size of maxPoolSize for low ram (Go) devices */
+    /* limitiometricMessageDeferralLogger the size of maxPoolSize for low ram (Go) devices */
     private fun adjustMaxSize(requestedMaxSize: Int): Int {
         return if (ActivityManager.isLowRamDeviceStatic()) {
             minOf(requestedMaxSize, 20) /* low ram max log size*/
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java
index 7f1ad6d..eeadf40 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java
@@ -23,7 +23,7 @@
 import javax.inject.Qualifier;
 
 /**
- * A {@link com.android.systemui.log.LogBuffer} for BiometricMessages processing such as
+ * A {@link com.android.systemui.plugins.log.LogBuffer} for BiometricMessages processing such as
  * {@link com.android.systemui.biometrics.FaceHelpMessageDeferral}
  */
 @Qualifier
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java
index 7d1f1c2..5cca1ab 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java
index 9ca0293..1d016d8 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java
index 7c5f402..c9f78bc 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt
new file mode 100644
index 0000000..0645236
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.plugins.log.LogBuffer] for keyguard clock logs. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class KeyguardClockLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java
index 08d969b..76d20be 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
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 1b81e88..9adb855 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -22,11 +22,11 @@
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.LogBufferFactory;
-import com.android.systemui.log.LogcatEchoTracker;
-import com.android.systemui.log.LogcatEchoTrackerDebug;
-import com.android.systemui.log.LogcatEchoTrackerProd;
+import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.plugins.log.LogcatEchoTracker;
+import com.android.systemui.plugins.log.LogcatEchoTrackerDebug;
+import com.android.systemui.plugins.log.LogcatEchoTrackerProd;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.util.Compile;
 
@@ -43,7 +43,7 @@
     @SysUISingleton
     @DozeLog
     public static LogBuffer provideDozeLogBuffer(LogBufferFactory factory) {
-        return factory.create("DozeLog", 120);
+        return factory.create("DozeLog", 150);
     }
 
     /** Provides a logging buffer for all logs related to the data layer of notifications. */
@@ -250,7 +250,7 @@
     /**
      * Provides a buffer for our connections and disconnections to MediaBrowserService.
      *
-     * See {@link com.android.systemui.media.ResumeMediaBrowser}.
+     * See {@link com.android.systemui.media.controls.resume.ResumeMediaBrowser}.
      */
     @Provides
     @SysUISingleton
@@ -262,7 +262,7 @@
     /**
      * Provides a buffer for updates to the media carousel.
      *
-     * See {@link com.android.systemui.media.MediaCarouselController}.
+     * See {@link com.android.systemui.media.controls.ui.MediaCarouselController}.
      */
     @Provides
     @SysUISingleton
@@ -316,6 +316,16 @@
     }
 
     /**
+     * Provides a {@link LogBuffer} for keyguard clock logs.
+     */
+    @Provides
+    @SysUISingleton
+    @KeyguardClockLog
+    public static LogBuffer provideKeyguardClockLog(LogBufferFactory factory) {
+        return factory.create("KeyguardClockLog", 500);
+    }
+
+    /**
      * Provides a {@link LogBuffer} for use by {@link com.android.keyguard.KeyguardUpdateMonitor}.
      */
     @Provides
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 1d7ba94..af43347 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
@@ -26,7 +26,7 @@
 import javax.inject.Qualifier;
 
 /**
- * A {@link LogBuffer} for {@link com.android.systemui.media.ResumeMediaBrowser}
+ * A {@link LogBuffer} for {@link com.android.systemui.media.controls.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 b03655a..f4dac6e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
@@ -26,7 +26,7 @@
 import javax.inject.Qualifier;
 
 /**
- * A {@link LogBuffer} for {@link com.android.systemui.media.MediaCarouselController}
+ * A {@link LogBuffer} for {@link com.android.systemui.media.controls.ui.MediaCarouselController}
  */
 @Qualifier
 @Documented
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java
index c67d8be..73690ab 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
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 53963fc..0c2cd92 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
@@ -26,7 +26,7 @@
 import javax.inject.Qualifier;
 
 /**
- * A {@link LogBuffer} for {@link com.android.systemui.media.MediaTimeoutLogger}
+ * A {@link LogBuffer} for {@link com.android.systemui.media.controls.pipeline.MediaTimeoutLogger}
  */
 @Qualifier
 @Documented
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java
index 5c572e8..1570d43 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java
index edab8c3..bf216c6 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
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 75a34fc..5b7f4bb 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
@@ -26,7 +26,7 @@
 import javax.inject.Qualifier;
 
 /**
- * A {@link LogBuffer} for {@link com.android.systemui.media.MediaViewLogger}
+ * A {@link LogBuffer} for {@link com.android.systemui.media.controls.ui.MediaViewLogger}
  */
 @Qualifier
 @Documented
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java
index b1c6dcf..6d91f0c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java
index 20fc6ff..26af496 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java
index fcc184a..61daf9c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java
index 760fbf3..a59afa0 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java
index a0b6864..6f8ea7f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java
index 8c8753a..835d349 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java
index 7259eeb..6e2bd7b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java
index e96e532..77b1bf5 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java
index 557a254..9fd166b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java
index dd5010c..dd168ba 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
index bd0d298..d24bfcb 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java
index b237f2d..67cdb72 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java
index f26b316..af0f7c5 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java
index dd68375..4c276e2 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java
index 8671dbf..ba8b27c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt
deleted file mode 100644
index 556560c..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * 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.media
-
-import android.animation.ArgbEvaluator
-import android.animation.ValueAnimator
-import android.animation.ValueAnimator.AnimatorUpdateListener
-import android.content.Context
-import android.content.res.ColorStateList
-import android.content.res.Configuration
-import android.content.res.Configuration.UI_MODE_NIGHT_YES
-import android.graphics.drawable.RippleDrawable
-import com.android.internal.R
-import com.android.internal.annotations.VisibleForTesting
-import com.android.settingslib.Utils
-import com.android.systemui.monet.ColorScheme
-
-/**
- * A [ColorTransition] is an object that updates the colors of views each time [updateColorScheme]
- * is triggered.
- */
-interface ColorTransition {
-    fun updateColorScheme(scheme: ColorScheme?): Boolean
-}
-
-/**
- * A [ColorTransition] that animates between two specific colors.
- * It uses a ValueAnimator to execute the animation and interpolate between the source color and
- * the target color.
- *
- * Selection of the target color from the scheme, and application of the interpolated color
- * are delegated to callbacks.
- */
-open class AnimatingColorTransition(
-    private val defaultColor: Int,
-    private val extractColor: (ColorScheme) -> Int,
-    private val applyColor: (Int) -> Unit
-) : AnimatorUpdateListener, ColorTransition {
-
-    private val argbEvaluator = ArgbEvaluator()
-    private val valueAnimator = buildAnimator()
-    var sourceColor: Int = defaultColor
-    var currentColor: Int = defaultColor
-    var targetColor: Int = defaultColor
-
-    override fun onAnimationUpdate(animation: ValueAnimator) {
-        currentColor = argbEvaluator.evaluate(
-            animation.animatedFraction, sourceColor, targetColor
-        ) as Int
-        applyColor(currentColor)
-    }
-
-    override fun updateColorScheme(scheme: ColorScheme?): Boolean {
-        val newTargetColor = if (scheme == null) defaultColor else extractColor(scheme)
-        if (newTargetColor != targetColor) {
-            sourceColor = currentColor
-            targetColor = newTargetColor
-            valueAnimator.cancel()
-            valueAnimator.start()
-            return true
-        }
-        return false
-    }
-
-    init {
-        applyColor(defaultColor)
-    }
-
-    @VisibleForTesting
-    open fun buildAnimator(): ValueAnimator {
-        val animator = ValueAnimator.ofFloat(0f, 1f)
-        animator.duration = 333
-        animator.addUpdateListener(this)
-        return animator
-    }
-}
-
-typealias AnimatingColorTransitionFactory =
-            (Int, (ColorScheme) -> Int, (Int) -> Unit) -> AnimatingColorTransition
-
-/**
- * ColorSchemeTransition constructs a ColorTransition for each color in the scheme
- * that needs to be transitioned when changed. It also sets up the assignment functions for sending
- * the sending the interpolated colors to the appropriate views.
- */
-class ColorSchemeTransition internal constructor(
-    private val context: Context,
-    private val mediaViewHolder: MediaViewHolder,
-    animatingColorTransitionFactory: AnimatingColorTransitionFactory
-) {
-    constructor(context: Context, mediaViewHolder: MediaViewHolder) :
-        this(context, mediaViewHolder, ::AnimatingColorTransition)
-
-    val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95)
-    val surfaceColor = animatingColorTransitionFactory(
-        bgColor,
-        ::surfaceFromScheme
-    ) { surfaceColor ->
-        val colorList = ColorStateList.valueOf(surfaceColor)
-        mediaViewHolder.seamlessIcon.imageTintList = colorList
-        mediaViewHolder.seamlessText.setTextColor(surfaceColor)
-        mediaViewHolder.albumView.backgroundTintList = colorList
-        mediaViewHolder.gutsViewHolder.setSurfaceColor(surfaceColor)
-    }
-
-    val accentPrimary = animatingColorTransitionFactory(
-        loadDefaultColor(R.attr.textColorPrimary),
-        ::accentPrimaryFromScheme
-    ) { accentPrimary ->
-        val accentColorList = ColorStateList.valueOf(accentPrimary)
-        mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList
-        mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary)
-    }
-
-    val accentSecondary = animatingColorTransitionFactory(
-        loadDefaultColor(R.attr.textColorPrimary),
-        ::accentSecondaryFromScheme
-    ) { accentSecondary ->
-        val colorList = ColorStateList.valueOf(accentSecondary)
-        (mediaViewHolder.seamlessButton.background as? RippleDrawable)?.let {
-            it.setColor(colorList)
-            it.effectColor = colorList
-        }
-    }
-
-    val colorSeamless = animatingColorTransitionFactory(
-        loadDefaultColor(R.attr.textColorPrimary),
-        { colorScheme: ColorScheme ->
-            // A1-100 dark in dark theme, A1-200 in light theme
-            if (context.resources.configuration.uiMode and
-                    Configuration.UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES)
-                        colorScheme.accent1[2]
-                        else colorScheme.accent1[3]
-        }, { seamlessColor: Int ->
-            val accentColorList = ColorStateList.valueOf(seamlessColor)
-            mediaViewHolder.seamlessButton.backgroundTintList = accentColorList
-    })
-
-    val textPrimary = animatingColorTransitionFactory(
-        loadDefaultColor(R.attr.textColorPrimary),
-        ::textPrimaryFromScheme
-    ) { textPrimary ->
-        mediaViewHolder.titleText.setTextColor(textPrimary)
-        val textColorList = ColorStateList.valueOf(textPrimary)
-        mediaViewHolder.seekBar.thumb.setTintList(textColorList)
-        mediaViewHolder.seekBar.progressTintList = textColorList
-        mediaViewHolder.scrubbingElapsedTimeView.setTextColor(textColorList)
-        mediaViewHolder.scrubbingTotalTimeView.setTextColor(textColorList)
-        for (button in mediaViewHolder.getTransparentActionButtons()) {
-            button.imageTintList = textColorList
-        }
-        mediaViewHolder.gutsViewHolder.setTextPrimaryColor(textPrimary)
-    }
-
-    val textPrimaryInverse = animatingColorTransitionFactory(
-        loadDefaultColor(R.attr.textColorPrimaryInverse),
-        ::textPrimaryInverseFromScheme
-    ) { textPrimaryInverse ->
-        mediaViewHolder.actionPlayPause.imageTintList = ColorStateList.valueOf(textPrimaryInverse)
-    }
-
-    val textSecondary = animatingColorTransitionFactory(
-        loadDefaultColor(R.attr.textColorSecondary),
-        ::textSecondaryFromScheme
-    ) { textSecondary -> mediaViewHolder.artistText.setTextColor(textSecondary) }
-
-    val textTertiary = animatingColorTransitionFactory(
-        loadDefaultColor(R.attr.textColorTertiary),
-        ::textTertiaryFromScheme
-    ) { textTertiary ->
-        mediaViewHolder.seekBar.progressBackgroundTintList = ColorStateList.valueOf(textTertiary)
-    }
-
-    val colorTransitions = arrayOf(
-        surfaceColor,
-        colorSeamless,
-        accentPrimary,
-        accentSecondary,
-        textPrimary,
-        textPrimaryInverse,
-        textSecondary,
-        textTertiary,
-    )
-
-    private fun loadDefaultColor(id: Int): Int {
-        return Utils.getColorAttr(context, id).defaultColor
-    }
-
-    fun updateColorScheme(colorScheme: ColorScheme?): Boolean {
-        var anyChanged = false
-        colorTransitions.forEach { anyChanged = it.updateColorScheme(colorScheme) || anyChanged }
-        colorScheme?.let { mediaViewHolder.gutsViewHolder.colorScheme = colorScheme }
-        return anyChanged
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
deleted file mode 100644
index 80bff83..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ /dev/null
@@ -1,1143 +0,0 @@
-package com.android.systemui.media
-
-import android.app.PendingIntent
-import android.content.Context
-import android.content.Intent
-import android.content.res.ColorStateList
-import android.content.res.Configuration
-import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
-import android.util.Log
-import android.util.MathUtils
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.view.animation.PathInterpolator
-import android.widget.LinearLayout
-import androidx.annotation.VisibleForTesting
-import com.android.internal.logging.InstanceId
-import com.android.systemui.Dumpable
-import com.android.systemui.R
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.MediaControlPanel.SMARTSPACE_CARD_DISMISS_EVENT
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.PageIndicator
-import com.android.systemui.shared.system.SysUiStatsLog
-import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.Utils
-import com.android.systemui.util.animation.UniqueObjectHostView
-import com.android.systemui.util.animation.requiresRemeasuring
-import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.time.SystemClock
-import com.android.systemui.util.traceSection
-import java.io.PrintWriter
-import java.util.TreeMap
-import javax.inject.Inject
-import javax.inject.Provider
-
-private const val TAG = "MediaCarouselController"
-private val settingsIntent = Intent().setAction(ACTION_MEDIA_CONTROLS_SETTINGS)
-private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
-
-/**
- * Class that is responsible for keeping the view carousel up to date.
- * This also handles changes in state and applies them to the media carousel like the expansion.
- */
-@SysUISingleton
-class MediaCarouselController @Inject constructor(
-    private val context: Context,
-    private val mediaControlPanelFactory: Provider<MediaControlPanel>,
-    private val visualStabilityProvider: VisualStabilityProvider,
-    private val mediaHostStatesManager: MediaHostStatesManager,
-    private val activityStarter: ActivityStarter,
-    private val systemClock: SystemClock,
-    @Main executor: DelayableExecutor,
-    private val mediaManager: MediaDataManager,
-    configurationController: ConfigurationController,
-    falsingCollector: FalsingCollector,
-    falsingManager: FalsingManager,
-    dumpManager: DumpManager,
-    private val logger: MediaUiEventLogger,
-    private val debugLogger: MediaCarouselControllerLogger
-) : Dumpable {
-    /**
-     * The current width of the carousel
-     */
-    private var currentCarouselWidth: Int = 0
-
-    /**
-     * The current height of the carousel
-     */
-    private var currentCarouselHeight: Int = 0
-
-    /**
-     * Are we currently showing only active players
-     */
-    private var currentlyShowingOnlyActive: Boolean = false
-
-    /**
-     * Is the player currently visible (at the end of the transformation
-     */
-    private var playersVisible: Boolean = false
-    /**
-     * The desired location where we'll be at the end of the transformation. Usually this matches
-     * the end location, except when we're still waiting on a state update call.
-     */
-    @MediaLocation
-    private var desiredLocation: Int = -1
-
-    /**
-     * The ending location of the view where it ends when all animations and transitions have
-     * finished
-     */
-    @MediaLocation
-    @VisibleForTesting
-    var currentEndLocation: Int = -1
-
-    /**
-     * The ending location of the view where it ends when all animations and transitions have
-     * finished
-     */
-    @MediaLocation
-    private var currentStartLocation: Int = -1
-
-    /**
-     * The progress of the transition or 1.0 if there is no transition happening
-     */
-    private var currentTransitionProgress: Float = 1.0f
-
-    /**
-     * The measured width of the carousel
-     */
-    private var carouselMeasureWidth: Int = 0
-
-    /**
-     * The measured height of the carousel
-     */
-    private var carouselMeasureHeight: Int = 0
-    private var desiredHostState: MediaHostState? = null
-    private val mediaCarousel: MediaScrollView
-    val mediaCarouselScrollHandler: MediaCarouselScrollHandler
-    val mediaFrame: ViewGroup
-    @VisibleForTesting
-    lateinit var settingsButton: View
-        private set
-    private val mediaContent: ViewGroup
-    @VisibleForTesting
-    val pageIndicator: PageIndicator
-    private val visualStabilityCallback: OnReorderingAllowedListener
-    private var needsReordering: Boolean = false
-    private var keysNeedRemoval = mutableSetOf<String>()
-    var shouldScrollToActivePlayer: Boolean = false
-    private var isRtl: Boolean = false
-        set(value) {
-            if (value != field) {
-                field = value
-                mediaFrame.layoutDirection =
-                        if (value) View.LAYOUT_DIRECTION_RTL else View.LAYOUT_DIRECTION_LTR
-                mediaCarouselScrollHandler.scrollToStart()
-            }
-        }
-    private var currentlyExpanded = true
-        set(value) {
-            if (field != value) {
-                field = value
-                for (player in MediaPlayerData.players()) {
-                    player.setListening(field)
-                }
-            }
-        }
-
-    companion object {
-        const val ANIMATION_BASE_DURATION = 2200f
-        const val DURATION = 167f
-        const val DETAILS_DELAY = 1067f
-        const val CONTROLS_DELAY = 1400f
-        const val PAGINATION_DELAY = 1900f
-        const val MEDIATITLES_DELAY = 1000f
-        const val MEDIACONTAINERS_DELAY = 967f
-        val TRANSFORM_BEZIER = PathInterpolator (0.68F, 0F, 0F, 1F)
-        val REVERSE_BEZIER = PathInterpolator (0F, 0.68F, 1F, 0F)
-
-        fun calculateAlpha(squishinessFraction: Float, delay: Float, duration: Float): Float {
-            val transformStartFraction = delay / ANIMATION_BASE_DURATION
-            val transformDurationFraction = duration / ANIMATION_BASE_DURATION
-            val squishinessToTime = REVERSE_BEZIER.getInterpolation(squishinessFraction)
-            return MathUtils.constrain((squishinessToTime - transformStartFraction) /
-                    transformDurationFraction, 0F, 1F)
-        }
-    }
-
-    private val configListener = object : ConfigurationController.ConfigurationListener {
-        override fun onDensityOrFontScaleChanged() {
-            // System font changes should only happen when UMO is offscreen or a flicker may occur
-            updatePlayers(recreateMedia = true)
-            inflateSettingsButton()
-        }
-
-        override fun onThemeChanged() {
-            updatePlayers(recreateMedia = false)
-            inflateSettingsButton()
-        }
-
-        override fun onConfigChanged(newConfig: Configuration?) {
-            if (newConfig == null) return
-            isRtl = newConfig.layoutDirection == View.LAYOUT_DIRECTION_RTL
-        }
-
-        override fun onUiModeChanged() {
-            updatePlayers(recreateMedia = false)
-            inflateSettingsButton()
-        }
-    }
-
-    /**
-     * Update MediaCarouselScrollHandler.visibleToUser to reflect media card container visibility.
-     * It will be called when the container is out of view.
-     */
-    lateinit var updateUserVisibility: () -> Unit
-    lateinit var updateHostVisibility: () -> Unit
-
-    private val isReorderingAllowed: Boolean
-        get() = visualStabilityProvider.isReorderingAllowed
-
-    init {
-        dumpManager.registerDumpable(TAG, this)
-        mediaFrame = inflateMediaCarousel()
-        mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller)
-        pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
-        mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator,
-                executor, this::onSwipeToDismiss, this::updatePageIndicatorLocation,
-                this::closeGuts, falsingCollector, falsingManager, this::logSmartspaceImpression,
-                logger)
-        isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
-        inflateSettingsButton()
-        mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
-        configurationController.addCallback(configListener)
-        visualStabilityCallback = OnReorderingAllowedListener {
-            if (needsReordering) {
-                needsReordering = false
-                reorderAllPlayers(previousVisiblePlayerKey = null)
-            }
-
-            keysNeedRemoval.forEach {
-                removePlayer(it)
-            }
-            if (keysNeedRemoval.size > 0) {
-                // Carousel visibility may need to be updated after late removals
-                updateHostVisibility()
-            }
-            keysNeedRemoval.clear()
-
-            // Update user visibility so that no extra impression will be logged when
-            // activeMediaIndex resets to 0
-            if (this::updateUserVisibility.isInitialized) {
-                updateUserVisibility()
-            }
-
-            // Let's reset our scroll position
-            mediaCarouselScrollHandler.scrollToStart()
-        }
-        visualStabilityProvider.addPersistentReorderingAllowedListener(visualStabilityCallback)
-        mediaManager.addListener(object : MediaDataManager.Listener {
-            override fun onMediaDataLoaded(
-                key: String,
-                oldKey: String?,
-                data: MediaData,
-                immediately: Boolean,
-                receivedSmartspaceCardLatency: Int,
-                isSsReactivated: Boolean
-            ) {
-                debugLogger.logMediaLoaded(key)
-                if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated)) {
-                    // Log card received if a new resumable media card is added
-                    MediaPlayerData.getMediaPlayer(key)?.let {
-                        /* ktlint-disable max-line-length */
-                        logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
-                                it.mSmartspaceId,
-                                it.mUid,
-                                surfaces = intArrayOf(
-                                        SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
-                                        SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
-                                        SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY),
-                                rank = MediaPlayerData.getMediaPlayerIndex(key))
-                        /* ktlint-disable max-line-length */
-                    }
-                    if (mediaCarouselScrollHandler.visibleToUser &&
-                            mediaCarouselScrollHandler.visibleMediaIndex
-                            == MediaPlayerData.getMediaPlayerIndex(key)) {
-                        logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded)
-                    }
-                } else if (receivedSmartspaceCardLatency != 0) {
-                    // Log resume card received if resumable media card is reactivated and
-                    // resume card is ranked first
-                    MediaPlayerData.players().forEachIndexed { index, it ->
-                        if (it.recommendationViewHolder == null) {
-                            it.mSmartspaceId = SmallHash.hash(it.mUid +
-                                    systemClock.currentTimeMillis().toInt())
-                            it.mIsImpressed = false
-                            /* ktlint-disable max-line-length */
-                            logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
-                                    it.mSmartspaceId,
-                                    it.mUid,
-                                    surfaces = intArrayOf(
-                                            SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
-                                            SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
-                                            SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY),
-                                    rank = index,
-                                    receivedLatencyMillis = receivedSmartspaceCardLatency)
-                            /* ktlint-disable max-line-length */
-                        }
-                    }
-                    // If media container area already visible to the user, log impression for
-                    // reactivated card.
-                    if (mediaCarouselScrollHandler.visibleToUser &&
-                            !mediaCarouselScrollHandler.qsExpanded) {
-                        logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded)
-                    }
-                }
-
-                val canRemove = data.isPlaying?.let { !it } ?: data.isClearable && !data.active
-                if (canRemove && !Utils.useMediaResumption(context)) {
-                    // This view isn't playing, let's remove this! This happens e.g when
-                    // dismissing/timing out a view. We still have the data around because
-                    // resumption could be on, but we should save the resources and release this.
-                    if (isReorderingAllowed) {
-                        onMediaDataRemoved(key)
-                    } else {
-                        keysNeedRemoval.add(key)
-                    }
-                } else {
-                    keysNeedRemoval.remove(key)
-                }
-            }
-
-            override fun onSmartspaceMediaDataLoaded(
-                key: String,
-                data: SmartspaceMediaData,
-                shouldPrioritize: Boolean
-            ) {
-                debugLogger.logRecommendationLoaded(key)
-                // Log the case where the hidden media carousel with the existed inactive resume
-                // media is shown by the Smartspace signal.
-                if (data.isActive) {
-                    val hasActivatedExistedResumeMedia =
-                            !mediaManager.hasActiveMedia() &&
-                                    mediaManager.hasAnyMedia() &&
-                                    shouldPrioritize
-                    if (hasActivatedExistedResumeMedia) {
-                        // Log resume card received if resumable media card is reactivated and
-                        // recommendation card is valid and ranked first
-                        MediaPlayerData.players().forEachIndexed { index, it ->
-                            if (it.recommendationViewHolder == null) {
-                                it.mSmartspaceId = SmallHash.hash(it.mUid +
-                                        systemClock.currentTimeMillis().toInt())
-                                it.mIsImpressed = false
-                                /* ktlint-disable max-line-length */
-                                logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
-                                        it.mSmartspaceId,
-                                        it.mUid,
-                                        surfaces = intArrayOf(
-                                                SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
-                                                SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
-                                                SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY),
-                                        rank = index,
-                                        receivedLatencyMillis = (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis).toInt())
-                                /* ktlint-disable max-line-length */
-                            }
-                        }
-                    }
-                    addSmartspaceMediaRecommendations(key, data, shouldPrioritize)
-                    MediaPlayerData.getMediaPlayer(key)?.let {
-                        /* ktlint-disable max-line-length */
-                        logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
-                                it.mSmartspaceId,
-                                it.mUid,
-                                surfaces = intArrayOf(
-                                        SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
-                                        SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
-                                        SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY),
-                                rank = MediaPlayerData.getMediaPlayerIndex(key),
-                                receivedLatencyMillis = (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis).toInt())
-                        /* ktlint-disable max-line-length */
-                    }
-                    if (mediaCarouselScrollHandler.visibleToUser &&
-                            mediaCarouselScrollHandler.visibleMediaIndex
-                            == MediaPlayerData.getMediaPlayerIndex(key)) {
-                        logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded)
-                    }
-                } else {
-                    onSmartspaceMediaDataRemoved(data.targetId, immediately = true)
-                }
-            }
-
-            override fun onMediaDataRemoved(key: String) {
-                debugLogger.logMediaRemoved(key)
-                removePlayer(key)
-            }
-
-            override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
-                debugLogger.logRecommendationRemoved(key, immediately)
-                if (immediately || isReorderingAllowed) {
-                    removePlayer(key)
-                    if (!immediately) {
-                        // Although it wasn't requested, we were able to process the removal
-                        // immediately since reordering is allowed. So, notify hosts to update
-                        if (this@MediaCarouselController::updateHostVisibility.isInitialized) {
-                            updateHostVisibility()
-                        }
-                    }
-                } else {
-                    keysNeedRemoval.add(key)
-                }
-            }
-        })
-        mediaFrame.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
-            // The pageIndicator is not laid out yet when we get the current state update,
-            // Lets make sure we have the right dimensions
-            updatePageIndicatorLocation()
-        }
-        mediaHostStatesManager.addCallback(object : MediaHostStatesManager.Callback {
-            override fun onHostStateChanged(location: Int, mediaHostState: MediaHostState) {
-                if (location == desiredLocation) {
-                    onDesiredLocationChanged(desiredLocation, mediaHostState, animate = false)
-                }
-            }
-        })
-    }
-
-    private fun inflateSettingsButton() {
-        val settings = LayoutInflater.from(context).inflate(R.layout.media_carousel_settings_button,
-                mediaFrame, false) as View
-        if (this::settingsButton.isInitialized) {
-            mediaFrame.removeView(settingsButton)
-        }
-        settingsButton = settings
-        mediaFrame.addView(settingsButton)
-        mediaCarouselScrollHandler.onSettingsButtonUpdated(settings)
-        settingsButton.setOnClickListener {
-            logger.logCarouselSettings()
-            activityStarter.startActivity(settingsIntent, true /* dismissShade */)
-        }
-    }
-
-    private fun inflateMediaCarousel(): ViewGroup {
-        val mediaCarousel = LayoutInflater.from(context).inflate(R.layout.media_carousel,
-                UniqueObjectHostView(context), false) as ViewGroup
-        // Because this is inflated when not attached to the true view hierarchy, it resolves some
-        // potential issues to force that the layout direction is defined by the locale
-        // (rather than inherited from the parent, which would resolve to LTR when unattached).
-        mediaCarousel.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
-        return mediaCarousel
-    }
-
-    private fun reorderAllPlayers(previousVisiblePlayerKey: MediaPlayerData.MediaSortKey?) {
-        mediaContent.removeAllViews()
-        for (mediaPlayer in MediaPlayerData.players()) {
-            mediaPlayer.mediaViewHolder?.let {
-                mediaContent.addView(it.player)
-            } ?: mediaPlayer.recommendationViewHolder?.let {
-                mediaContent.addView(it.recommendations)
-            }
-        }
-        mediaCarouselScrollHandler.onPlayersChanged()
-
-        // Automatically scroll to the active player if needed
-        if (shouldScrollToActivePlayer) {
-            shouldScrollToActivePlayer = false
-            val activeMediaIndex = MediaPlayerData.firstActiveMediaIndex()
-            if (activeMediaIndex != -1) {
-                previousVisiblePlayerKey?.let {
-                    val previousVisibleIndex = MediaPlayerData.playerKeys()
-                            .indexOfFirst { key -> it == key }
-                    mediaCarouselScrollHandler
-                            .scrollToPlayer(previousVisibleIndex, activeMediaIndex)
-                } ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = activeMediaIndex)
-            }
-        }
-    }
-
-    // Returns true if new player is added
-    private fun addOrUpdatePlayer(
-        key: String,
-        oldKey: String?,
-        data: MediaData,
-        isSsReactivated: Boolean
-    ): Boolean = traceSection("MediaCarouselController#addOrUpdatePlayer") {
-        MediaPlayerData.moveIfExists(oldKey, key)
-        val existingPlayer = MediaPlayerData.getMediaPlayer(key)
-        val curVisibleMediaKey = MediaPlayerData.playerKeys()
-                .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
-        val isCurVisibleMediaPlaying = curVisibleMediaKey?.data?.isPlaying
-        if (existingPlayer == null) {
-            val newPlayer = mediaControlPanelFactory.get()
-            newPlayer.attachPlayer(MediaViewHolder.create(
-                    LayoutInflater.from(context), mediaContent))
-            newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
-            val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
-                    ViewGroup.LayoutParams.WRAP_CONTENT)
-            newPlayer.mediaViewHolder?.player?.setLayoutParams(lp)
-            newPlayer.bindPlayer(data, key)
-            newPlayer.setListening(currentlyExpanded)
-            MediaPlayerData.addMediaPlayer(
-                key, data, newPlayer, systemClock, isSsReactivated, debugLogger
-            )
-            updatePlayerToState(newPlayer, noAnimation = true)
-            if (data.active) {
-                reorderAllPlayers(curVisibleMediaKey)
-            } else {
-                needsReordering = true
-            }
-        } else {
-            existingPlayer.bindPlayer(data, key)
-            MediaPlayerData.addMediaPlayer(
-                key, data, existingPlayer, systemClock, isSsReactivated, debugLogger
-            )
-            // Check the playing status of both current visible and new media players
-            // To make sure we scroll to the active playing media card.
-            if (isReorderingAllowed ||
-                    shouldScrollToActivePlayer &&
-                    data.isPlaying == true &&
-                    isCurVisibleMediaPlaying == false
-            ) {
-                reorderAllPlayers(curVisibleMediaKey)
-            } else {
-                needsReordering = true
-            }
-        }
-        updatePageIndicator()
-        mediaCarouselScrollHandler.onPlayersChanged()
-        mediaFrame.requiresRemeasuring = true
-        // Check postcondition: mediaContent should have the same number of children as there are
-        // elements in mediaPlayers.
-        if (MediaPlayerData.players().size != mediaContent.childCount) {
-            Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync")
-        }
-        return existingPlayer == null
-    }
-
-    private fun addSmartspaceMediaRecommendations(
-        key: String,
-        data: SmartspaceMediaData,
-        shouldPrioritize: Boolean
-    ) = traceSection("MediaCarouselController#addSmartspaceMediaRecommendations") {
-        if (DEBUG) Log.d(TAG, "Updating smartspace target in carousel")
-        if (MediaPlayerData.getMediaPlayer(key) != null) {
-            Log.w(TAG, "Skip adding smartspace target in carousel")
-            return
-        }
-
-        val existingSmartspaceMediaKey = MediaPlayerData.smartspaceMediaKey()
-        existingSmartspaceMediaKey?.let {
-            val removedPlayer = MediaPlayerData.removeMediaPlayer(existingSmartspaceMediaKey)
-            removedPlayer?.run { debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey) }
-        }
-
-        val newRecs = mediaControlPanelFactory.get()
-        newRecs.attachRecommendation(
-                RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent))
-        newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
-        val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT)
-        newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp)
-        newRecs.bindRecommendation(data)
-        val curVisibleMediaKey = MediaPlayerData.playerKeys()
-                .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
-        MediaPlayerData.addMediaRecommendation(
-            key, data, newRecs, shouldPrioritize, systemClock, debugLogger
-        )
-        updatePlayerToState(newRecs, noAnimation = true)
-        reorderAllPlayers(curVisibleMediaKey)
-        updatePageIndicator()
-        mediaFrame.requiresRemeasuring = true
-        // Check postcondition: mediaContent should have the same number of children as there are
-        // elements in mediaPlayers.
-        if (MediaPlayerData.players().size != mediaContent.childCount) {
-            Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync")
-        }
-    }
-
-    fun removePlayer(
-        key: String,
-        dismissMediaData: Boolean = true,
-        dismissRecommendation: Boolean = true
-    ) {
-        if (key == MediaPlayerData.smartspaceMediaKey()) {
-            MediaPlayerData.smartspaceMediaData?.let {
-                logger.logRecommendationRemoved(it.packageName, it.instanceId)
-            }
-        }
-        val removed = MediaPlayerData.removeMediaPlayer(key)
-        removed?.apply {
-            mediaCarouselScrollHandler.onPrePlayerRemoved(removed)
-            mediaContent.removeView(removed.mediaViewHolder?.player)
-            mediaContent.removeView(removed.recommendationViewHolder?.recommendations)
-            removed.onDestroy()
-            mediaCarouselScrollHandler.onPlayersChanged()
-            updatePageIndicator()
-
-            if (dismissMediaData) {
-                // Inform the media manager of a potentially late dismissal
-                mediaManager.dismissMediaData(key, delay = 0L)
-            }
-            if (dismissRecommendation) {
-                // Inform the media manager of a potentially late dismissal
-                mediaManager.dismissSmartspaceRecommendation(key, delay = 0L)
-            }
-        }
-    }
-
-    private fun updatePlayers(recreateMedia: Boolean) {
-        pageIndicator.tintList = ColorStateList.valueOf(
-            context.getColor(R.color.media_paging_indicator)
-        )
-
-        MediaPlayerData.mediaData().forEach { (key, data, isSsMediaRec) ->
-            if (isSsMediaRec) {
-                val smartspaceMediaData = MediaPlayerData.smartspaceMediaData
-                removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
-                smartspaceMediaData?.let {
-                    addSmartspaceMediaRecommendations(
-                            it.targetId, it, MediaPlayerData.shouldPrioritizeSs)
-                }
-            } else {
-                val isSsReactivated = MediaPlayerData.isSsReactivated(key)
-                if (recreateMedia) {
-                    removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
-                }
-                addOrUpdatePlayer(
-                        key = key, oldKey = null, data = data, isSsReactivated = isSsReactivated)
-            }
-        }
-    }
-
-    private fun updatePageIndicator() {
-        val numPages = mediaContent.getChildCount()
-        pageIndicator.setNumPages(numPages)
-        if (numPages == 1) {
-            pageIndicator.setLocation(0f)
-        }
-        updatePageIndicatorAlpha()
-    }
-
-    /**
-     * Set a new interpolated state for all players. This is a state that is usually controlled
-     * by a finger movement where the user drags from one state to the next.
-     *
-     * @param startLocation the start location of our state or -1 if this is directly set
-     * @param endLocation the ending location of our state.
-     * @param progress the progress of the transition between startLocation and endlocation. If
-     *                 this is not a guided transformation, this will be 1.0f
-     * @param immediately should this state be applied immediately, canceling all animations?
-     */
-    fun setCurrentState(
-        @MediaLocation startLocation: Int,
-        @MediaLocation endLocation: Int,
-        progress: Float,
-        immediately: Boolean
-    ) {
-        if (startLocation != currentStartLocation ||
-                endLocation != currentEndLocation ||
-                progress != currentTransitionProgress ||
-                immediately
-        ) {
-            currentStartLocation = startLocation
-            currentEndLocation = endLocation
-            currentTransitionProgress = progress
-            for (mediaPlayer in MediaPlayerData.players()) {
-                updatePlayerToState(mediaPlayer, immediately)
-            }
-            maybeResetSettingsCog()
-            updatePageIndicatorAlpha()
-        }
-    }
-
-    @VisibleForTesting
-    fun updatePageIndicatorAlpha() {
-        val hostStates = mediaHostStatesManager.mediaHostStates
-        val endIsVisible = hostStates[currentEndLocation]?.visible ?: false
-        val startIsVisible = hostStates[currentStartLocation]?.visible ?: false
-        val startAlpha = if (startIsVisible) 1.0f else 0.0f
-        // when squishing in split shade, only use endState, which keeps changing
-        // to provide squishFraction
-        val squishFraction = hostStates[currentEndLocation]?.squishFraction ?: 1.0F
-        val endAlpha = (if (endIsVisible) 1.0f else 0.0f) *
-                calculateAlpha(squishFraction, PAGINATION_DELAY, DURATION)
-        var alpha = 1.0f
-        if (!endIsVisible || !startIsVisible) {
-            var progress = currentTransitionProgress
-            if (!endIsVisible) {
-                progress = 1.0f - progress
-            }
-            // Let's fade in quickly at the end where the view is visible
-            progress = MathUtils.constrain(
-                    MathUtils.map(0.95f, 1.0f, 0.0f, 1.0f, progress),
-                    0.0f,
-                    1.0f)
-            alpha = MathUtils.lerp(startAlpha, endAlpha, progress)
-        }
-        pageIndicator.alpha = alpha
-    }
-
-    private fun updatePageIndicatorLocation() {
-        // Update the location of the page indicator, carousel clipping
-        val translationX = if (isRtl) {
-            (pageIndicator.width - currentCarouselWidth) / 2.0f
-        } else {
-            (currentCarouselWidth - pageIndicator.width) / 2.0f
-        }
-        pageIndicator.translationX = translationX + mediaCarouselScrollHandler.contentTranslation
-        val layoutParams = pageIndicator.layoutParams as ViewGroup.MarginLayoutParams
-        pageIndicator.translationY = (currentCarouselHeight - pageIndicator.height -
-                layoutParams.bottomMargin).toFloat()
-    }
-
-    /**
-     * Update the dimension of this carousel.
-     */
-    private fun updateCarouselDimensions() {
-        var width = 0
-        var height = 0
-        for (mediaPlayer in MediaPlayerData.players()) {
-            val controller = mediaPlayer.mediaViewController
-            // When transitioning the view to gone, the view gets smaller, but the translation
-            // Doesn't, let's add the translation
-            width = Math.max(width, controller.currentWidth + controller.translationX.toInt())
-            height = Math.max(height, controller.currentHeight + controller.translationY.toInt())
-        }
-        if (width != currentCarouselWidth || height != currentCarouselHeight) {
-            currentCarouselWidth = width
-            currentCarouselHeight = height
-            mediaCarouselScrollHandler.setCarouselBounds(
-                    currentCarouselWidth, currentCarouselHeight)
-            updatePageIndicatorLocation()
-            updatePageIndicatorAlpha()
-        }
-    }
-
-    private fun maybeResetSettingsCog() {
-        val hostStates = mediaHostStatesManager.mediaHostStates
-        val endShowsActive = hostStates[currentEndLocation]?.showsOnlyActiveMedia
-                ?: true
-        val startShowsActive = hostStates[currentStartLocation]?.showsOnlyActiveMedia
-                ?: endShowsActive
-        if (currentlyShowingOnlyActive != endShowsActive ||
-                ((currentTransitionProgress != 1.0f && currentTransitionProgress != 0.0f) &&
-                        startShowsActive != endShowsActive)) {
-            // Whenever we're transitioning from between differing states or the endstate differs
-            // we reset the translation
-            currentlyShowingOnlyActive = endShowsActive
-            mediaCarouselScrollHandler.resetTranslation(animate = true)
-        }
-    }
-
-    private fun updatePlayerToState(mediaPlayer: MediaControlPanel, noAnimation: Boolean) {
-        mediaPlayer.mediaViewController.setCurrentState(
-                startLocation = currentStartLocation,
-                endLocation = currentEndLocation,
-                transitionProgress = currentTransitionProgress,
-                applyImmediately = noAnimation)
-    }
-
-    /**
-     * The desired location of this view has changed. We should remeasure the view to match
-     * the new bounds and kick off bounds animations if necessary.
-     * If an animation is happening, an animation is kicked of externally, which sets a new
-     * current state until we reach the targetState.
-     *
-     * @param desiredLocation the location we're going to
-     * @param desiredHostState the target state we're transitioning to
-     * @param animate should this be animated
-     */
-    fun onDesiredLocationChanged(
-        desiredLocation: Int,
-        desiredHostState: MediaHostState?,
-        animate: Boolean,
-        duration: Long = 200,
-        startDelay: Long = 0
-    ) = traceSection("MediaCarouselController#onDesiredLocationChanged") {
-        desiredHostState?.let {
-            if (this.desiredLocation != desiredLocation) {
-                // Only log an event when location changes
-                logger.logCarouselPosition(desiredLocation)
-            }
-
-            // This is a hosting view, let's remeasure our players
-            this.desiredLocation = desiredLocation
-            this.desiredHostState = it
-            currentlyExpanded = it.expansion > 0
-
-            val shouldCloseGuts = !currentlyExpanded &&
-                    !mediaManager.hasActiveMediaOrRecommendation() &&
-                    desiredHostState.showsOnlyActiveMedia
-
-            for (mediaPlayer in MediaPlayerData.players()) {
-                if (animate) {
-                    mediaPlayer.mediaViewController.animatePendingStateChange(
-                            duration = duration,
-                            delay = startDelay)
-                }
-                if (shouldCloseGuts && mediaPlayer.mediaViewController.isGutsVisible) {
-                    mediaPlayer.closeGuts(!animate)
-                }
-
-                mediaPlayer.mediaViewController.onLocationPreChange(desiredLocation)
-            }
-            mediaCarouselScrollHandler.showsSettingsButton = !it.showsOnlyActiveMedia
-            mediaCarouselScrollHandler.falsingProtectionNeeded = it.falsingProtectionNeeded
-            val nowVisible = it.visible
-            if (nowVisible != playersVisible) {
-                playersVisible = nowVisible
-                if (nowVisible) {
-                    mediaCarouselScrollHandler.resetTranslation()
-                }
-            }
-            updateCarouselSize()
-        }
-    }
-
-    fun closeGuts(immediate: Boolean = true) {
-        MediaPlayerData.players().forEach {
-            it.closeGuts(immediate)
-        }
-    }
-
-    /**
-     * Update the size of the carousel, remeasuring it if necessary.
-     */
-    private fun updateCarouselSize() {
-        val width = desiredHostState?.measurementInput?.width ?: 0
-        val height = desiredHostState?.measurementInput?.height ?: 0
-        if (width != carouselMeasureWidth && width != 0 ||
-                height != carouselMeasureHeight && height != 0) {
-            carouselMeasureWidth = width
-            carouselMeasureHeight = height
-            val playerWidthPlusPadding = carouselMeasureWidth +
-                    context.resources.getDimensionPixelSize(R.dimen.qs_media_padding)
-            // Let's remeasure the carousel
-            val widthSpec = desiredHostState?.measurementInput?.widthMeasureSpec ?: 0
-            val heightSpec = desiredHostState?.measurementInput?.heightMeasureSpec ?: 0
-            mediaCarousel.measure(widthSpec, heightSpec)
-            mediaCarousel.layout(0, 0, width, mediaCarousel.measuredHeight)
-            // Update the padding after layout; view widths are used in RTL to calculate scrollX
-            mediaCarouselScrollHandler.playerWidthPlusPadding = playerWidthPlusPadding
-        }
-    }
-
-    /**
-     * Log the user impression for media card at visibleMediaIndex.
-     */
-    fun logSmartspaceImpression(qsExpanded: Boolean) {
-        val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex
-        if (MediaPlayerData.players().size > visibleMediaIndex) {
-            val mediaControlPanel = MediaPlayerData.players().elementAt(visibleMediaIndex)
-            val hasActiveMediaOrRecommendationCard =
-                    MediaPlayerData.hasActiveMediaOrRecommendationCard()
-            if (!hasActiveMediaOrRecommendationCard && !qsExpanded) {
-                // Skip logging if on LS or QQS, and there is no active media card
-                return
-            }
-            logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN
-                    mediaControlPanel.mSmartspaceId,
-                    mediaControlPanel.mUid,
-                    intArrayOf(mediaControlPanel.surfaceForSmartspaceLogging))
-            mediaControlPanel.mIsImpressed = true
-        }
-    }
-
-    @JvmOverloads
-    /**
-     * Log Smartspace events
-     *
-     * @param eventId UI event id (e.g. 800 for SMARTSPACE_CARD_SEEN)
-     * @param instanceId id to uniquely identify a card, e.g. each headphone generates a new
-     * instanceId
-     * @param uid uid for the application that media comes from
-     * @param surfaces list of display surfaces the media card is on (e.g. lockscreen, shade) when
-     * the event happened
-     * @param interactedSubcardRank the rank for interacted media item for recommendation card, -1
-     * for tapping on card but not on any media item, 0 for first media item, 1 for second, etc.
-     * @param interactedSubcardCardinality how many media items were shown to the user when there
-     * is user interaction
-     * @param rank the rank for media card in the media carousel, starting from 0
-     * @param receivedLatencyMillis latency in milliseconds for card received events. E.g. latency
-     * between headphone connection to sysUI displays media recommendation card
-     * @param isSwipeToDismiss whether is to log swipe-to-dismiss event
-     *
-     */
-    fun logSmartspaceCardReported(
-        eventId: Int,
-        instanceId: Int,
-        uid: Int,
-        surfaces: IntArray,
-        interactedSubcardRank: Int = 0,
-        interactedSubcardCardinality: Int = 0,
-        rank: Int = mediaCarouselScrollHandler.visibleMediaIndex,
-        receivedLatencyMillis: Int = 0,
-        isSwipeToDismiss: Boolean = false
-    ) {
-        if (MediaPlayerData.players().size <= rank) {
-            return
-        }
-
-        val mediaControlKey = MediaPlayerData.playerKeys().elementAt(rank)
-        // Only log media resume card when Smartspace data is available
-        if (!mediaControlKey.isSsMediaRec &&
-                !mediaManager.smartspaceMediaData.isActive &&
-                MediaPlayerData.smartspaceMediaData == null) {
-            return
-        }
-
-        val cardinality = mediaContent.getChildCount()
-        surfaces.forEach { surface ->
-            /* ktlint-disable max-line-length */
-            SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
-                    eventId,
-                    instanceId,
-                    // Deprecated, replaced with AiAi feature type so we don't need to create logging
-                    // card type for each new feature.
-                    SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD,
-                    surface,
-                    // Use -1 as rank value to indicate user swipe to dismiss the card
-                    if (isSwipeToDismiss) -1 else rank,
-                    cardinality,
-                    if (mediaControlKey.isSsMediaRec)
-                        15 // MEDIA_RECOMMENDATION
-                    else if (mediaControlKey.isSsReactivated)
-                        43 // MEDIA_RESUME_SS_ACTIVATED
-                    else
-                        31, // MEDIA_RESUME
-                    uid,
-                    interactedSubcardRank,
-                    interactedSubcardCardinality,
-                    receivedLatencyMillis,
-                    null, // Media cards cannot have subcards.
-                    null // Media cards don't have dimensions today.
-            )
-            /* ktlint-disable max-line-length */
-            if (DEBUG) {
-                Log.d(TAG, "Log Smartspace card event id: $eventId instance id: $instanceId" +
-                        " surface: $surface rank: $rank cardinality: $cardinality " +
-                        "isRecommendationCard: ${mediaControlKey.isSsMediaRec} " +
-                        "isSsReactivated: ${mediaControlKey.isSsReactivated}" +
-                        "uid: $uid " +
-                        "interactedSubcardRank: $interactedSubcardRank " +
-                        "interactedSubcardCardinality: $interactedSubcardCardinality " +
-                        "received_latency_millis: $receivedLatencyMillis")
-            }
-        }
-    }
-
-    private fun onSwipeToDismiss() {
-        MediaPlayerData.players().forEachIndexed {
-            index, it ->
-            if (it.mIsImpressed) {
-                logSmartspaceCardReported(SMARTSPACE_CARD_DISMISS_EVENT,
-                        it.mSmartspaceId,
-                        it.mUid,
-                        intArrayOf(it.surfaceForSmartspaceLogging),
-                        rank = index,
-                        isSwipeToDismiss = true)
-                // Reset card impressed state when swipe to dismissed
-                it.mIsImpressed = false
-            }
-        }
-        logger.logSwipeDismiss()
-        mediaManager.onSwipeToDismiss()
-    }
-
-    fun getCurrentVisibleMediaContentIntent(): PendingIntent? {
-        return MediaPlayerData.playerKeys()
-                .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)?.data?.clickIntent
-    }
-
-    override fun dump(pw: PrintWriter, args: Array<out String>) {
-        pw.apply {
-            println("keysNeedRemoval: $keysNeedRemoval")
-            println("dataKeys: ${MediaPlayerData.dataKeys()}")
-            println("playerSortKeys: ${MediaPlayerData.playerKeys()}")
-            println("smartspaceMediaData: ${MediaPlayerData.smartspaceMediaData}")
-            println("shouldPrioritizeSs: ${MediaPlayerData.shouldPrioritizeSs}")
-            println("current size: $currentCarouselWidth x $currentCarouselHeight")
-            println("location: $desiredLocation")
-            println("state: ${desiredHostState?.expansion}, " +
-                "only active ${desiredHostState?.showsOnlyActiveMedia}")
-        }
-    }
-}
-
-@VisibleForTesting
-internal object MediaPlayerData {
-    private val EMPTY = MediaData(
-            userId = -1,
-            initialized = false,
-            app = null,
-            appIcon = null,
-            artist = null,
-            song = null,
-            artwork = null,
-            actions = emptyList(),
-            actionsToShowInCompact = emptyList(),
-            packageName = "INVALID",
-            token = null,
-            clickIntent = null,
-            device = null,
-            active = true,
-            resumeAction = null,
-            instanceId = InstanceId.fakeInstanceId(-1),
-            appUid = -1)
-    // Whether should prioritize Smartspace card.
-    internal var shouldPrioritizeSs: Boolean = false
-        private set
-    internal var smartspaceMediaData: SmartspaceMediaData? = null
-        private set
-
-    data class MediaSortKey(
-        val isSsMediaRec: Boolean, // Whether the item represents a Smartspace media recommendation.
-        val data: MediaData,
-        val updateTime: Long = 0,
-        val isSsReactivated: Boolean = false
-    )
-
-    private val comparator = compareByDescending<MediaSortKey> {
-            it.data.isPlaying == true && it.data.playbackLocation == MediaData.PLAYBACK_LOCAL }
-        .thenByDescending {
-            it.data.isPlaying == true && it.data.playbackLocation == MediaData.PLAYBACK_CAST_LOCAL }
-        .thenByDescending { it.data.active }
-        .thenByDescending { shouldPrioritizeSs == it.isSsMediaRec }
-        .thenByDescending { !it.data.resumption }
-        .thenByDescending { it.data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE }
-        .thenByDescending { it.data.lastActive }
-        .thenByDescending { it.updateTime }
-        .thenByDescending { it.data.notificationKey }
-
-    private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
-    private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
-
-    fun addMediaPlayer(
-        key: String,
-        data: MediaData,
-        player: MediaControlPanel,
-        clock: SystemClock,
-        isSsReactivated: Boolean,
-        debugLogger: MediaCarouselControllerLogger? = null
-    ) {
-        val removedPlayer = removeMediaPlayer(key)
-        if (removedPlayer != null && removedPlayer != player) {
-            debugLogger?.logPotentialMemoryLeak(key)
-        }
-        val sortKey = MediaSortKey(isSsMediaRec = false,
-                data, clock.currentTimeMillis(), isSsReactivated = isSsReactivated)
-        mediaData.put(key, sortKey)
-        mediaPlayers.put(sortKey, player)
-    }
-
-    fun addMediaRecommendation(
-        key: String,
-        data: SmartspaceMediaData,
-        player: MediaControlPanel,
-        shouldPrioritize: Boolean,
-        clock: SystemClock,
-        debugLogger: MediaCarouselControllerLogger? = null
-    ) {
-        shouldPrioritizeSs = shouldPrioritize
-        val removedPlayer = removeMediaPlayer(key)
-        if (removedPlayer != null && removedPlayer != player) {
-            debugLogger?.logPotentialMemoryLeak(key)
-        }
-        val sortKey = MediaSortKey(isSsMediaRec = true,
-            EMPTY.copy(isPlaying = false), clock.currentTimeMillis(), isSsReactivated = true)
-        mediaData.put(key, sortKey)
-        mediaPlayers.put(sortKey, player)
-        smartspaceMediaData = data
-    }
-
-    fun moveIfExists(
-        oldKey: String?,
-        newKey: String,
-        debugLogger: MediaCarouselControllerLogger? = null
-    ) {
-        if (oldKey == null || oldKey == newKey) {
-            return
-        }
-
-        mediaData.remove(oldKey)?.let {
-            val removedPlayer = removeMediaPlayer(newKey)
-            removedPlayer?.run { debugLogger?.logPotentialMemoryLeak(newKey) }
-            mediaData.put(newKey, it)
-        }
-    }
-
-    fun getMediaPlayer(key: String): MediaControlPanel? {
-        return mediaData.get(key)?.let { mediaPlayers.get(it) }
-    }
-
-    fun getMediaPlayerIndex(key: String): Int {
-        val sortKey = mediaData.get(key)
-        mediaPlayers.entries.forEachIndexed { index, e ->
-            if (e.key == sortKey) {
-                return index
-            }
-        }
-        return -1
-    }
-
-    fun removeMediaPlayer(key: String) = mediaData.remove(key)?.let {
-        if (it.isSsMediaRec) {
-            smartspaceMediaData = null
-        }
-        mediaPlayers.remove(it)
-    }
-
-    fun mediaData() = mediaData.entries.map { e -> Triple(e.key, e.value.data, e.value.isSsMediaRec) }
-
-    fun dataKeys() = mediaData.keys
-
-    fun players() = mediaPlayers.values
-
-    fun playerKeys() = mediaPlayers.keys
-
-    /** Returns the index of the first non-timeout media. */
-    fun firstActiveMediaIndex(): Int {
-        mediaPlayers.entries.forEachIndexed { index, e ->
-            if (!e.key.isSsMediaRec && e.key.data.active) {
-                return index
-            }
-        }
-        return -1
-    }
-
-    /** Returns the existing Smartspace target id. */
-    fun smartspaceMediaKey(): String? {
-        mediaData.entries.forEach { e ->
-            if (e.value.isSsMediaRec) {
-                return e.key
-            }
-        }
-        return null
-    }
-
-    @VisibleForTesting
-    fun clear() {
-        mediaData.clear()
-        mediaPlayers.clear()
-    }
-
-    /* Returns true if there is active media player card or recommendation card */
-    fun hasActiveMediaOrRecommendationCard(): Boolean {
-        if (smartspaceMediaData != null && smartspaceMediaData?.isActive!!) {
-            return true
-        }
-        if (firstActiveMediaIndex() != -1) {
-            return true
-        }
-        return false
-    }
-
-    fun isSsReactivated(key: String): Boolean = mediaData.get(key)?.isSsReactivated ?: false
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt
deleted file mode 100644
index b1018f9..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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.media
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.dagger.MediaCarouselControllerLog
-import javax.inject.Inject
-
-/** A debug logger for [MediaCarouselController]. */
-@SysUISingleton
-class MediaCarouselControllerLogger @Inject constructor(
-    @MediaCarouselControllerLog private val buffer: LogBuffer
-) {
-    /**
-     * Log that there might be a potential memory leak for the [MediaControlPanel] and/or
-     * [MediaViewController] related to [key].
-     */
-    fun logPotentialMemoryLeak(key: String) = buffer.log(
-        TAG,
-        LogLevel.DEBUG,
-        { str1 = key },
-        {
-            "Potential memory leak: " +
-                    "Removing control panel for $str1 from map without calling #onDestroy"
-        }
-    )
-
-    fun logMediaLoaded(key: String) = buffer.log(
-        TAG,
-        LogLevel.DEBUG,
-        { str1 = key },
-        { "add player $str1" }
-    )
-
-    fun logMediaRemoved(key: String) = buffer.log(
-        TAG,
-        LogLevel.DEBUG,
-        { str1 = key },
-        { "removing player $str1" }
-    )
-
-    fun logRecommendationLoaded(key: String) = buffer.log(
-        TAG,
-        LogLevel.DEBUG,
-        { str1 = key },
-        { "add recommendation $str1" }
-    )
-
-    fun logRecommendationRemoved(key: String, immediately: Boolean) = buffer.log(
-        TAG,
-        LogLevel.DEBUG,
-        {
-            str1 = key
-            bool1 = immediately
-        },
-        { "removing recommendation $str1, immediate=$bool1" }
-    )
-}
-
-private const val TAG = "MediaCarouselCtlrLog"
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
deleted file mode 100644
index e0b6d1f..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ /dev/null
@@ -1,1243 +0,0 @@
-/*
- * Copyright (C) 2020 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.media
-
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.ValueAnimator
-import android.annotation.IntDef
-import android.content.Context
-import android.content.res.Configuration
-import android.database.ContentObserver
-import android.graphics.Rect
-import android.net.Uri
-import android.os.Handler
-import android.os.UserHandle
-import android.provider.Settings
-import android.util.Log
-import android.util.MathUtils
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewGroupOverlay
-import androidx.annotation.VisibleForTesting
-import com.android.keyguard.KeyguardViewController
-import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dreams.DreamOverlayStateController
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.media.dream.MediaDreamComplication
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.NotifPanelEvents
-import com.android.systemui.statusbar.CrossFadeHelper
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.SysuiStatusBarStateController
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator
-import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.LargeScreenUtils
-import com.android.systemui.util.animation.UniqueObjectHostView
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.traceSection
-import javax.inject.Inject
-
-private val TAG: String = MediaHierarchyManager::class.java.simpleName
-
-/**
- * Similarly to isShown but also excludes views that have 0 alpha
- */
-val View.isShownNotFaded: Boolean
-    get() {
-        var current: View = this
-        while (true) {
-            if (current.visibility != View.VISIBLE) {
-                return false
-            }
-            if (current.alpha == 0.0f) {
-                return false
-            }
-            val parent = current.parent ?: return false // We are not attached to the view root
-            if (parent !is View) {
-                // we reached the viewroot, hurray
-                return true
-            }
-            current = parent
-        }
-    }
-
-/**
- * This manager is responsible for placement of the unique media view between the different hosts
- * and animate the positions of the views to achieve seamless transitions.
- */
-@SysUISingleton
-class MediaHierarchyManager @Inject constructor(
-    private val context: Context,
-    private val statusBarStateController: SysuiStatusBarStateController,
-    private val keyguardStateController: KeyguardStateController,
-    private val bypassController: KeyguardBypassController,
-    private val mediaCarouselController: MediaCarouselController,
-    private val keyguardViewController: KeyguardViewController,
-    private val dreamOverlayStateController: DreamOverlayStateController,
-    configurationController: ConfigurationController,
-    wakefulnessLifecycle: WakefulnessLifecycle,
-    panelEventsEvents: NotifPanelEvents,
-    private val secureSettings: SecureSettings,
-    @Main private val handler: Handler,
-) {
-
-    /**
-     * Track the media player setting status on lock screen.
-     */
-    private var allowMediaPlayerOnLockScreen: Boolean = true
-    private val lockScreenMediaPlayerUri =
-            secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN)
-
-    /**
-     * Whether we "skip" QQS during panel expansion.
-     *
-     * This means that when expanding the panel we go directly to QS. Also when we are on QS and
-     * start closing the panel, it fully collapses instead of going to QQS.
-     */
-    private var skipQqsOnExpansion: Boolean = false
-
-    /**
-     * The root overlay of the hierarchy. This is where the media notification is attached to
-     * whenever the view is transitioning from one host to another. It also make sure that the
-     * view is always in its final state when it is attached to a view host.
-     */
-    private var rootOverlay: ViewGroupOverlay? = null
-
-    private var rootView: View? = null
-    private var currentBounds = Rect()
-    private var animationStartBounds: Rect = Rect()
-
-    private var animationStartClipping = Rect()
-    private var currentClipping = Rect()
-    private var targetClipping = Rect()
-
-    /**
-     * The cross fade progress at the start of the animation. 0.5f means it's just switching between
-     * the start and the end location and the content is fully faded, while 0.75f means that we're
-     * halfway faded in again in the target state.
-     */
-    private var animationStartCrossFadeProgress = 0.0f
-
-    /**
-     * The starting alpha of the animation
-     */
-    private var animationStartAlpha = 0.0f
-
-    /**
-     * The starting location of the cross fade if an animation is running right now.
-     */
-    @MediaLocation
-    private var crossFadeAnimationStartLocation = -1
-
-    /**
-     * The end location of the cross fade if an animation is running right now.
-     */
-    @MediaLocation
-    private var crossFadeAnimationEndLocation = -1
-    private var targetBounds: Rect = Rect()
-    private val mediaFrame
-        get() = mediaCarouselController.mediaFrame
-    private var statusbarState: Int = statusBarStateController.state
-    private var animator = ValueAnimator.ofFloat(0.0f, 1.0f).apply {
-        interpolator = Interpolators.FAST_OUT_SLOW_IN
-        addUpdateListener {
-            updateTargetState()
-            val currentAlpha: Float
-            var boundsProgress = animatedFraction
-            if (isCrossFadeAnimatorRunning) {
-                animationCrossFadeProgress = MathUtils.lerp(animationStartCrossFadeProgress, 1.0f,
-                    animatedFraction)
-                // When crossfading, let's keep the bounds at the right location during fading
-                boundsProgress = if (animationCrossFadeProgress < 0.5f) 0.0f else 1.0f
-                currentAlpha = calculateAlphaFromCrossFade(animationCrossFadeProgress)
-            } else {
-                // If we're not crossfading, let's interpolate from the start alpha to 1.0f
-                currentAlpha = MathUtils.lerp(animationStartAlpha, 1.0f, animatedFraction)
-            }
-            interpolateBounds(animationStartBounds, targetBounds, boundsProgress,
-                    result = currentBounds)
-            resolveClipping(currentClipping)
-            applyState(currentBounds, currentAlpha, clipBounds = currentClipping)
-        }
-        addListener(object : AnimatorListenerAdapter() {
-            private var cancelled: Boolean = false
-
-            override fun onAnimationCancel(animation: Animator?) {
-                cancelled = true
-                animationPending = false
-                rootView?.removeCallbacks(startAnimation)
-            }
-
-            override fun onAnimationEnd(animation: Animator?) {
-                isCrossFadeAnimatorRunning = false
-                if (!cancelled) {
-                    applyTargetStateIfNotAnimating()
-                }
-            }
-
-            override fun onAnimationStart(animation: Animator?) {
-                cancelled = false
-                animationPending = false
-            }
-        })
-    }
-
-    private fun resolveClipping(result: Rect) {
-        if (animationStartClipping.isEmpty) result.set(targetClipping)
-        else if (targetClipping.isEmpty) result.set(animationStartClipping)
-        else result.setIntersect(animationStartClipping, targetClipping)
-    }
-
-    private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_DREAM_OVERLAY + 1)
-    /**
-     * The last location where this view was at before going to the desired location. This is
-     * useful for guided transitions.
-     */
-    @MediaLocation
-    private var previousLocation = -1
-    /**
-     * The desired location where the view will be at the end of the transition.
-     */
-    @MediaLocation
-    private var desiredLocation = -1
-
-    /**
-     * The current attachment location where the view is currently attached.
-     * Usually this matches the desired location except for animations whenever a view moves
-     * to the new desired location, during which it is in [IN_OVERLAY].
-     */
-    @MediaLocation
-    private var currentAttachmentLocation = -1
-
-    private var inSplitShade = false
-
-    /**
-     * Is there any active media in the carousel?
-     */
-    private var hasActiveMedia: Boolean = false
-        get() = mediaHosts.get(LOCATION_QQS)?.visible == true
-
-    /**
-     * Are we currently waiting on an animation to start?
-     */
-    private var animationPending: Boolean = false
-    private val startAnimation: Runnable = Runnable { animator.start() }
-
-    /**
-     * The expansion of quick settings
-     */
-    var qsExpansion: Float = 0.0f
-        set(value) {
-            if (field != value) {
-                field = value
-                updateDesiredLocation()
-                if (getQSTransformationProgress() >= 0) {
-                    updateTargetState()
-                    applyTargetStateIfNotAnimating()
-                }
-            }
-        }
-
-    /**
-     * Is quick setting expanded?
-     */
-    var qsExpanded: Boolean = false
-        set(value) {
-            if (field != value) {
-                field = value
-                mediaCarouselController.mediaCarouselScrollHandler.qsExpanded = value
-            }
-            // qs is expanded on LS shade and HS shade
-            if (value && (isLockScreenShadeVisibleToUser() || isHomeScreenShadeVisibleToUser())) {
-                mediaCarouselController.logSmartspaceImpression(value)
-            }
-            mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
-        }
-
-    /**
-     * distance that the full shade transition takes in order for media to fully transition to the
-     * shade
-     */
-    private var distanceForFullShadeTransition = 0
-
-    /**
-     * The amount of progress we are currently in if we're transitioning to the full shade.
-     * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
-     * shade.
-     */
-    private var fullShadeTransitionProgress = 0f
-        set(value) {
-            if (field == value) {
-                return
-            }
-            field = value
-            if (bypassController.bypassEnabled || statusbarState != StatusBarState.KEYGUARD) {
-                // No need to do all the calculations / updates below if we're not on the lockscreen
-                // or if we're bypassing.
-                return
-            }
-            updateDesiredLocation(forceNoAnimation = isCurrentlyFading())
-            if (value >= 0) {
-                updateTargetState()
-                // Setting the alpha directly, as the below call will use it to update the alpha
-                carouselAlpha = calculateAlphaFromCrossFade(field)
-                applyTargetStateIfNotAnimating()
-            }
-        }
-
-    /**
-     * Is there currently a cross-fade animation running driven by an animator?
-     */
-    private var isCrossFadeAnimatorRunning = false
-
-    /**
-     * Are we currently transitionioning from the lockscreen to the full shade
-     * [StatusBarState.SHADE_LOCKED] or [StatusBarState.SHADE]. Once the user has dragged down and
-     * the transition starts, this will no longer return true.
-     */
-    private val isTransitioningToFullShade: Boolean
-        get() = fullShadeTransitionProgress != 0f && !bypassController.bypassEnabled &&
-            statusbarState == StatusBarState.KEYGUARD
-
-    /**
-     * Set the amount of pixels we have currently dragged down if we're transitioning to the full
-     * shade. 0.0f means we're not transitioning yet.
-     */
-    fun setTransitionToFullShadeAmount(value: Float) {
-        // If we're transitioning starting on the shade_locked, we don't want any delay and rather
-        // have it aligned with the rest of the animation
-        val progress = MathUtils.saturate(value / distanceForFullShadeTransition)
-        fullShadeTransitionProgress = progress
-    }
-
-    /**
-     * Returns the amount of translationY of the media container, during the current guided
-     * transformation, if running. If there is no guided transformation running, it will return 0.
-     */
-    fun getGuidedTransformationTranslationY(): Int {
-        if (!isCurrentlyInGuidedTransformation()) {
-            return -1
-        }
-        val startHost = getHost(previousLocation) ?: return 0
-        return targetBounds.top - startHost.currentBounds.top
-    }
-
-    /**
-     * Is the shade currently collapsing from the expanded qs? If we're on the lockscreen and in qs,
-     * we wouldn't want to transition in that case.
-     */
-    var collapsingShadeFromQS: Boolean = false
-        set(value) {
-            if (field != value) {
-                field = value
-                updateDesiredLocation(forceNoAnimation = true)
-            }
-        }
-
-    /**
-     * Are location changes currently blocked?
-     */
-    private val blockLocationChanges: Boolean
-        get() {
-            return goingToSleep || dozeAnimationRunning
-        }
-
-    /**
-     * Are we currently going to sleep
-     */
-    private var goingToSleep: Boolean = false
-        set(value) {
-            if (field != value) {
-                field = value
-                if (!value) {
-                    updateDesiredLocation()
-                }
-            }
-        }
-
-    /**
-     * Are we currently fullyAwake
-     */
-    private var fullyAwake: Boolean = false
-        set(value) {
-            if (field != value) {
-                field = value
-                if (value) {
-                    updateDesiredLocation(forceNoAnimation = true)
-                }
-            }
-        }
-
-    /**
-     * Is the doze animation currently Running
-     */
-    private var dozeAnimationRunning: Boolean = false
-        private set(value) {
-            if (field != value) {
-                field = value
-                if (!value) {
-                    updateDesiredLocation()
-                }
-            }
-        }
-
-    /**
-     * Is the dream overlay currently active
-     */
-    private var dreamOverlayActive: Boolean = false
-        private set(value) {
-            if (field != value) {
-                field = value
-                updateDesiredLocation(forceNoAnimation = true)
-            }
-        }
-
-    /**
-     * Is the dream media complication currently active
-     */
-    private var dreamMediaComplicationActive: Boolean = false
-        private set(value) {
-            if (field != value) {
-                field = value
-                updateDesiredLocation(forceNoAnimation = true)
-            }
-        }
-
-    /**
-     * The current cross fade progress. 0.5f means it's just switching
-     * between the start and the end location and the content is fully faded, while 0.75f means
-     * that we're halfway faded in again in the target state.
-     * This is only valid while [isCrossFadeAnimatorRunning] is true.
-     */
-    private var animationCrossFadeProgress = 1.0f
-
-    /**
-     * The current carousel Alpha.
-     */
-    private var carouselAlpha: Float = 1.0f
-        set(value) {
-            if (field == value) {
-                return
-            }
-            field = value
-            CrossFadeHelper.fadeIn(mediaFrame, value)
-        }
-
-    /**
-     * Calculate the alpha of the view when given a cross-fade progress.
-     *
-     * @param crossFadeProgress The current cross fade progress. 0.5f means it's just switching
-     * between the start and the end location and the content is fully faded, while 0.75f means
-     * that we're halfway faded in again in the target state.
-     */
-    private fun calculateAlphaFromCrossFade(crossFadeProgress: Float): Float {
-        if (crossFadeProgress <= 0.5f) {
-            return 1.0f - crossFadeProgress / 0.5f
-        } else {
-            return (crossFadeProgress - 0.5f) / 0.5f
-        }
-    }
-
-    init {
-        updateConfiguration()
-        configurationController.addCallback(object : ConfigurationController.ConfigurationListener {
-            override fun onConfigChanged(newConfig: Configuration?) {
-                updateConfiguration()
-                updateDesiredLocation(forceNoAnimation = true, forceStateUpdate = true)
-            }
-        })
-        statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
-            override fun onStatePreChange(oldState: Int, newState: Int) {
-                // We're updating the location before the state change happens, since we want the
-                // location of the previous state to still be up to date when the animation starts
-                statusbarState = newState
-                updateDesiredLocation()
-            }
-
-            override fun onStateChanged(newState: Int) {
-                updateTargetState()
-                // Enters shade from lock screen
-                if (newState == StatusBarState.SHADE_LOCKED && isLockScreenShadeVisibleToUser()) {
-                    mediaCarouselController.logSmartspaceImpression(qsExpanded)
-                }
-                mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
-            }
-
-            override fun onDozeAmountChanged(linear: Float, eased: Float) {
-                dozeAnimationRunning = linear != 0.0f && linear != 1.0f
-            }
-
-            override fun onDozingChanged(isDozing: Boolean) {
-                if (!isDozing) {
-                    dozeAnimationRunning = false
-                    // Enters lock screen from screen off
-                    if (isLockScreenVisibleToUser()) {
-                        mediaCarouselController.logSmartspaceImpression(qsExpanded)
-                    }
-                } else {
-                    updateDesiredLocation()
-                    qsExpanded = false
-                    closeGuts()
-                }
-                mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
-            }
-
-            override fun onExpandedChanged(isExpanded: Boolean) {
-                // Enters shade from home screen
-                if (isHomeScreenShadeVisibleToUser()) {
-                    mediaCarouselController.logSmartspaceImpression(qsExpanded)
-                }
-                mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
-            }
-        })
-
-        dreamOverlayStateController.addCallback(object : DreamOverlayStateController.Callback {
-            override fun onComplicationsChanged() {
-                dreamMediaComplicationActive = dreamOverlayStateController.complications.any {
-                    it is MediaDreamComplication
-                }
-            }
-
-            override fun onStateChanged() {
-                dreamOverlayStateController.isOverlayActive.also { dreamOverlayActive = it }
-            }
-        })
-
-        wakefulnessLifecycle.addObserver(object : WakefulnessLifecycle.Observer {
-            override fun onFinishedGoingToSleep() {
-                goingToSleep = false
-            }
-
-            override fun onStartedGoingToSleep() {
-                goingToSleep = true
-                fullyAwake = false
-            }
-
-            override fun onFinishedWakingUp() {
-                goingToSleep = false
-                fullyAwake = true
-            }
-
-            override fun onStartedWakingUp() {
-                goingToSleep = false
-            }
-        })
-
-        mediaCarouselController.updateUserVisibility = {
-            mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
-        }
-        mediaCarouselController.updateHostVisibility = {
-            mediaHosts.forEach {
-                it?.updateViewVisibility()
-            }
-        }
-
-        panelEventsEvents.registerListener(object : NotifPanelEvents.Listener {
-            override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {
-                skipQqsOnExpansion = isExpandImmediateEnabled
-                updateDesiredLocation()
-            }
-        })
-
-        val settingsObserver: ContentObserver = object : ContentObserver(handler) {
-            override fun onChange(selfChange: Boolean, uri: Uri?) {
-                if (uri == lockScreenMediaPlayerUri) {
-                    allowMediaPlayerOnLockScreen =
-                            secureSettings.getBoolForUser(
-                                    Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
-                                    true,
-                                    UserHandle.USER_CURRENT
-                            )
-                }
-            }
-        }
-        secureSettings.registerContentObserverForUser(
-                Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
-                settingsObserver,
-                UserHandle.USER_ALL)
-    }
-
-    private fun updateConfiguration() {
-        distanceForFullShadeTransition = context.resources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_media_transition_distance)
-        inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(context.resources)
-    }
-
-    /**
-     * Register a media host and create a view can be attached to a view hierarchy
-     * and where the players will be placed in when the host is the currently desired state.
-     *
-     * @return the hostView associated with this location
-     */
-    fun register(mediaObject: MediaHost): UniqueObjectHostView {
-        val viewHost = createUniqueObjectHost()
-        mediaObject.hostView = viewHost
-        mediaObject.addVisibilityChangeListener {
-            // If QQS changes visibility, we need to force an update to ensure the transition
-            // goes into the correct state
-            val stateUpdate = mediaObject.location == LOCATION_QQS
-
-            // Never animate because of a visibility change, only state changes should do that
-            updateDesiredLocation(forceNoAnimation = true, forceStateUpdate = stateUpdate)
-        }
-        mediaHosts[mediaObject.location] = mediaObject
-        if (mediaObject.location == desiredLocation) {
-            // In case we are overriding a view that is already visible, make sure we attach it
-            // to this new host view in the below call
-            desiredLocation = -1
-        }
-        if (mediaObject.location == currentAttachmentLocation) {
-            currentAttachmentLocation = -1
-        }
-        updateDesiredLocation()
-        return viewHost
-    }
-
-    /**
-     * Close the guts in all players in [MediaCarouselController].
-     */
-    fun closeGuts() {
-        mediaCarouselController.closeGuts()
-    }
-
-    private fun createUniqueObjectHost(): UniqueObjectHostView {
-        val viewHost = UniqueObjectHostView(context)
-        viewHost.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
-            override fun onViewAttachedToWindow(p0: View?) {
-                if (rootOverlay == null) {
-                    rootView = viewHost.viewRootImpl.view
-                    rootOverlay = (rootView!!.overlay as ViewGroupOverlay)
-                }
-                viewHost.removeOnAttachStateChangeListener(this)
-            }
-
-            override fun onViewDetachedFromWindow(p0: View?) {
-            }
-        })
-        return viewHost
-    }
-
-    /**
-     * Updates the location that the view should be in. If it changes, an animation may be triggered
-     * going from the old desired location to the new one.
-     *
-     * @param forceNoAnimation optional parameter telling the system not to animate
-     * @param forceStateUpdate optional parameter telling the system to update transition state
-     *                         even if location did not change
-     */
-    private fun updateDesiredLocation(
-        forceNoAnimation: Boolean = false,
-        forceStateUpdate: Boolean = false
-    ) = traceSection("MediaHierarchyManager#updateDesiredLocation") {
-        val desiredLocation = calculateLocation()
-        if (desiredLocation != this.desiredLocation || forceStateUpdate) {
-            if (this.desiredLocation >= 0 && desiredLocation != this.desiredLocation) {
-                // Only update previous location when it actually changes
-                previousLocation = this.desiredLocation
-            } else if (forceStateUpdate) {
-                val onLockscreen = (!bypassController.bypassEnabled &&
-                        (statusbarState == StatusBarState.KEYGUARD))
-                if (desiredLocation == LOCATION_QS && previousLocation == LOCATION_LOCKSCREEN &&
-                        !onLockscreen) {
-                    // If media active state changed and the device is now unlocked, update the
-                    // previous location so we animate between the correct hosts
-                    previousLocation = LOCATION_QQS
-                }
-            }
-            val isNewView = this.desiredLocation == -1
-            this.desiredLocation = desiredLocation
-            // Let's perform a transition
-            val animate = !forceNoAnimation &&
-                    shouldAnimateTransition(desiredLocation, previousLocation)
-            val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation)
-            val host = getHost(desiredLocation)
-            val willFade = calculateTransformationType() == TRANSFORMATION_TYPE_FADE
-            if (!willFade || isCurrentlyInGuidedTransformation() || !animate) {
-                // if we're fading, we want the desired location / measurement only to change
-                // once fully faded. This is happening in the host attachment
-                mediaCarouselController.onDesiredLocationChanged(desiredLocation, host,
-                    animate, animDuration, delay)
-            }
-            performTransitionToNewLocation(isNewView, animate)
-        }
-    }
-
-    private fun performTransitionToNewLocation(
-        isNewView: Boolean,
-        animate: Boolean
-    ) = traceSection("MediaHierarchyManager#performTransitionToNewLocation") {
-        if (previousLocation < 0 || isNewView) {
-            cancelAnimationAndApplyDesiredState()
-            return
-        }
-        val currentHost = getHost(desiredLocation)
-        val previousHost = getHost(previousLocation)
-        if (currentHost == null || previousHost == null) {
-            cancelAnimationAndApplyDesiredState()
-            return
-        }
-        updateTargetState()
-        if (isCurrentlyInGuidedTransformation()) {
-            applyTargetStateIfNotAnimating()
-        } else if (animate) {
-            val wasCrossFading = isCrossFadeAnimatorRunning
-            val previewsCrossFadeProgress = animationCrossFadeProgress
-            animator.cancel()
-            if (currentAttachmentLocation != previousLocation ||
-                    !previousHost.hostView.isAttachedToWindow) {
-                // Let's animate to the new position, starting from the current position
-                // We also go in here in case the view was detached, since the bounds wouldn't
-                // be correct anymore
-                animationStartBounds.set(currentBounds)
-                animationStartClipping.set(currentClipping)
-            } else {
-                // otherwise, let's take the freshest state, since the current one could
-                // be outdated
-                animationStartBounds.set(previousHost.currentBounds)
-                animationStartClipping.set(previousHost.currentClipping)
-            }
-            val transformationType = calculateTransformationType()
-            var needsCrossFade = transformationType == TRANSFORMATION_TYPE_FADE
-            var crossFadeStartProgress = 0.0f
-            // The alpha is only relevant when not cross fading
-            var newCrossFadeStartLocation = previousLocation
-            if (wasCrossFading) {
-                if (currentAttachmentLocation == crossFadeAnimationEndLocation) {
-                    if (needsCrossFade) {
-                        // We were previously crossFading and we've already reached
-                        // the end view, Let's start crossfading from the same position there
-                        crossFadeStartProgress = 1.0f - previewsCrossFadeProgress
-                    }
-                    // Otherwise let's fade in from the current alpha, but not cross fade
-                } else {
-                    // We haven't reached the previous location yet, let's still cross fade from
-                    // where we were.
-                    newCrossFadeStartLocation = crossFadeAnimationStartLocation
-                    if (newCrossFadeStartLocation == desiredLocation) {
-                        // we're crossFading back to where we were, let's start at the end position
-                        crossFadeStartProgress = 1.0f - previewsCrossFadeProgress
-                    } else {
-                        // Let's start from where we are right now
-                        crossFadeStartProgress = previewsCrossFadeProgress
-                        // We need to force cross fading as we haven't reached the end location yet
-                        needsCrossFade = true
-                    }
-                }
-            } else if (needsCrossFade) {
-                // let's not flicker and start with the same alpha
-                crossFadeStartProgress = (1.0f - carouselAlpha) / 2.0f
-            }
-            isCrossFadeAnimatorRunning = needsCrossFade
-            crossFadeAnimationStartLocation = newCrossFadeStartLocation
-            crossFadeAnimationEndLocation = desiredLocation
-            animationStartAlpha = carouselAlpha
-            animationStartCrossFadeProgress = crossFadeStartProgress
-            adjustAnimatorForTransition(desiredLocation, previousLocation)
-            if (!animationPending) {
-                rootView?.let {
-                    // Let's delay the animation start until we finished laying out
-                    animationPending = true
-                    it.postOnAnimation(startAnimation)
-                }
-            }
-        } else {
-            cancelAnimationAndApplyDesiredState()
-        }
-    }
-
-    private fun shouldAnimateTransition(
-        @MediaLocation currentLocation: Int,
-        @MediaLocation previousLocation: Int
-    ): Boolean {
-        if (isCurrentlyInGuidedTransformation()) {
-            return false
-        }
-        if (skipQqsOnExpansion) {
-            return false
-        }
-        // This is an invalid transition, and can happen when using the camera gesture from the
-        // lock screen. Disallow.
-        if (previousLocation == LOCATION_LOCKSCREEN &&
-            desiredLocation == LOCATION_QQS &&
-            statusbarState == StatusBarState.SHADE) {
-            return false
-        }
-
-        if (currentLocation == LOCATION_QQS &&
-                previousLocation == LOCATION_LOCKSCREEN &&
-                (statusBarStateController.leaveOpenOnKeyguardHide() ||
-                        statusbarState == StatusBarState.SHADE_LOCKED)) {
-            // Usually listening to the isShown is enough to determine this, but there is some
-            // non-trivial reattaching logic happening that will make the view not-shown earlier
-            return true
-        }
-
-        if (statusbarState == StatusBarState.KEYGUARD && (currentLocation == LOCATION_LOCKSCREEN ||
-                        previousLocation == LOCATION_LOCKSCREEN)) {
-            // We're always fading from lockscreen to keyguard in situations where the player
-            // is already fully hidden
-            return false
-        }
-        return mediaFrame.isShownNotFaded || animator.isRunning || animationPending
-    }
-
-    private fun adjustAnimatorForTransition(desiredLocation: Int, previousLocation: Int) {
-        val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation)
-        animator.apply {
-            duration = animDuration
-            startDelay = delay
-        }
-    }
-
-    private fun getAnimationParams(previousLocation: Int, desiredLocation: Int): Pair<Long, Long> {
-        var animDuration = 200L
-        var delay = 0L
-        if (previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QQS) {
-            // Going to the full shade, let's adjust the animation duration
-            if (statusbarState == StatusBarState.SHADE &&
-                    keyguardStateController.isKeyguardFadingAway) {
-                delay = keyguardStateController.keyguardFadingAwayDelay
-            }
-            animDuration = (StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE / 2f).toLong()
-        } else if (previousLocation == LOCATION_QQS && desiredLocation == LOCATION_LOCKSCREEN) {
-            animDuration = StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR.toLong()
-        }
-        return animDuration to delay
-    }
-
-    private fun applyTargetStateIfNotAnimating() {
-        if (!animator.isRunning) {
-            // Let's immediately apply the target state (which is interpolated) if there is
-            // no animation running. Otherwise the animation update will already update
-            // the location
-            applyState(targetBounds, carouselAlpha, clipBounds = targetClipping)
-        }
-    }
-
-    /**
-     * Updates the bounds that the view wants to be in at the end of the animation.
-     */
-    private fun updateTargetState() {
-        var starthost = getHost(previousLocation)
-        var endHost = getHost(desiredLocation)
-        if (isCurrentlyInGuidedTransformation() && !isCurrentlyFading() && starthost != null &&
-            endHost != null) {
-            val progress = getTransformationProgress()
-            // If either of the hosts are invisible, let's keep them at the other host location to
-            // have a nicer disappear animation. Otherwise the currentBounds of the state might
-            // be undefined
-            if (!endHost.visible) {
-                endHost = starthost
-            } else if (!starthost.visible) {
-                starthost = endHost
-            }
-            val newBounds = endHost.currentBounds
-            val previousBounds = starthost.currentBounds
-            targetBounds = interpolateBounds(previousBounds, newBounds, progress)
-            targetClipping = endHost.currentClipping
-        } else if (endHost != null) {
-            val bounds = endHost.currentBounds
-            targetBounds.set(bounds)
-            targetClipping = endHost.currentClipping
-        }
-    }
-
-    private fun interpolateBounds(
-        startBounds: Rect,
-        endBounds: Rect,
-        progress: Float,
-        result: Rect? = null
-    ): Rect {
-        val left = MathUtils.lerp(startBounds.left.toFloat(),
-                endBounds.left.toFloat(), progress).toInt()
-        val top = MathUtils.lerp(startBounds.top.toFloat(),
-                endBounds.top.toFloat(), progress).toInt()
-        val right = MathUtils.lerp(startBounds.right.toFloat(),
-                endBounds.right.toFloat(), progress).toInt()
-        val bottom = MathUtils.lerp(startBounds.bottom.toFloat(),
-                endBounds.bottom.toFloat(), progress).toInt()
-        val resultBounds = result ?: Rect()
-        resultBounds.set(left, top, right, bottom)
-        return resultBounds
-    }
-
-    /** @return true if this transformation is guided by an external progress like a finger */
-    fun isCurrentlyInGuidedTransformation(): Boolean {
-        return hasValidStartAndEndLocations() &&
-                getTransformationProgress() >= 0 &&
-                areGuidedTransitionHostsVisible()
-    }
-
-    private fun hasValidStartAndEndLocations(): Boolean {
-        return previousLocation != -1 && desiredLocation != -1
-    }
-
-    /**
-     * Calculate the transformation type for the current animation
-     */
-    @VisibleForTesting
-    @TransformationType
-    fun calculateTransformationType(): Int {
-        if (isTransitioningToFullShade) {
-            if (inSplitShade && areGuidedTransitionHostsVisible()) {
-                return TRANSFORMATION_TYPE_TRANSITION
-            }
-            return TRANSFORMATION_TYPE_FADE
-        }
-        if (previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QS ||
-            previousLocation == LOCATION_QS && desiredLocation == LOCATION_LOCKSCREEN) {
-            // animating between ls and qs should fade, as QS is clipped.
-            return TRANSFORMATION_TYPE_FADE
-        }
-        if (previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QQS) {
-            // animating between ls and qqs should fade when dragging down via e.g. expand button
-            return TRANSFORMATION_TYPE_FADE
-        }
-        return TRANSFORMATION_TYPE_TRANSITION
-    }
-
-    private fun areGuidedTransitionHostsVisible(): Boolean {
-        return getHost(previousLocation)?.visible == true &&
-                getHost(desiredLocation)?.visible == true
-    }
-
-    /**
-     * @return the current transformation progress if we're in a guided transformation and -1
-     * otherwise
-     */
-    private fun getTransformationProgress(): Float {
-        if (skipQqsOnExpansion) {
-            return -1.0f
-        }
-        val progress = getQSTransformationProgress()
-        if (statusbarState != StatusBarState.KEYGUARD && progress >= 0) {
-            return progress
-        }
-        if (isTransitioningToFullShade) {
-            return fullShadeTransitionProgress
-        }
-        return -1.0f
-    }
-
-    private fun getQSTransformationProgress(): Float {
-        val currentHost = getHost(desiredLocation)
-        val previousHost = getHost(previousLocation)
-        if (hasActiveMedia && (currentHost?.location == LOCATION_QS && !inSplitShade)) {
-            if (previousHost?.location == LOCATION_QQS) {
-                if (previousHost.visible || statusbarState != StatusBarState.KEYGUARD) {
-                    return qsExpansion
-                }
-            }
-        }
-        return -1.0f
-    }
-
-    private fun getHost(@MediaLocation location: Int): MediaHost? {
-        if (location < 0) {
-            return null
-        }
-        return mediaHosts[location]
-    }
-
-    private fun cancelAnimationAndApplyDesiredState() {
-        animator.cancel()
-        getHost(desiredLocation)?.let {
-            applyState(it.currentBounds, alpha = 1.0f, immediately = true)
-        }
-    }
-
-    /**
-     * Apply the current state to the view, updating it's bounds and desired state
-     */
-    private fun applyState(
-        bounds: Rect,
-        alpha: Float,
-        immediately: Boolean = false,
-        clipBounds: Rect = EMPTY_RECT
-    ) = traceSection("MediaHierarchyManager#applyState") {
-        currentBounds.set(bounds)
-        currentClipping = clipBounds
-        carouselAlpha = if (isCurrentlyFading()) alpha else 1.0f
-        val onlyUseEndState = !isCurrentlyInGuidedTransformation() || isCurrentlyFading()
-        val startLocation = if (onlyUseEndState) -1 else previousLocation
-        val progress = if (onlyUseEndState) 1.0f else getTransformationProgress()
-        val endLocation = resolveLocationForFading()
-        mediaCarouselController.setCurrentState(startLocation, endLocation, progress, immediately)
-        updateHostAttachment()
-        if (currentAttachmentLocation == IN_OVERLAY) {
-            // Setting the clipping on the hierarchy of `mediaFrame` does not work
-            if (!currentClipping.isEmpty) {
-                currentBounds.intersect(currentClipping)
-            }
-            mediaFrame.setLeftTopRightBottom(
-                    currentBounds.left,
-                    currentBounds.top,
-                    currentBounds.right,
-                    currentBounds.bottom)
-        }
-    }
-
-    private fun updateHostAttachment() = traceSection(
-        "MediaHierarchyManager#updateHostAttachment"
-    ) {
-        var newLocation = resolveLocationForFading()
-        var canUseOverlay = !isCurrentlyFading()
-        if (isCrossFadeAnimatorRunning) {
-            if (getHost(newLocation)?.visible == true &&
-                getHost(newLocation)?.hostView?.isShown == false &&
-                newLocation != desiredLocation) {
-                // We're crossfading but the view is already hidden. Let's move to the overlay
-                // instead. This happens when animating to the full shade using a button click.
-                canUseOverlay = true
-            }
-        }
-        val inOverlay = isTransitionRunning() && rootOverlay != null && canUseOverlay
-        newLocation = if (inOverlay) IN_OVERLAY else newLocation
-        if (currentAttachmentLocation != newLocation) {
-            currentAttachmentLocation = newLocation
-
-            // Remove the carousel from the old host
-            (mediaFrame.parent as ViewGroup?)?.removeView(mediaFrame)
-
-            // Add it to the new one
-            if (inOverlay) {
-                rootOverlay!!.add(mediaFrame)
-            } else {
-                val targetHost = getHost(newLocation)!!.hostView
-                // When adding back to the host, let's make sure to reset the bounds.
-                // Usually adding the view will trigger a layout that does this automatically,
-                // but we sometimes suppress this.
-                targetHost.addView(mediaFrame)
-                val left = targetHost.paddingLeft
-                val top = targetHost.paddingTop
-                mediaFrame.setLeftTopRightBottom(
-                        left,
-                        top,
-                        left + currentBounds.width(),
-                        top + currentBounds.height())
-
-                if (mediaFrame.childCount > 0) {
-                    val child = mediaFrame.getChildAt(0)
-                    if (mediaFrame.height < child.height) {
-                        Log.wtf(TAG, "mediaFrame height is too small for child: " +
-                            "${mediaFrame.height} vs ${child.height}")
-                    }
-                }
-            }
-            if (isCrossFadeAnimatorRunning) {
-                // When cross-fading with an animation, we only notify the media carousel of the
-                // location change, once the view is reattached to the new place and not immediately
-                // when the desired location changes. This callback will update the measurement
-                // of the carousel, only once we've faded out at the old location and then reattach
-                // to fade it in at the new location.
-                mediaCarouselController.onDesiredLocationChanged(
-                    newLocation,
-                    getHost(newLocation),
-                    animate = false
-                )
-            }
-        }
-    }
-
-    /**
-     * Calculate the location when cross fading between locations. While fading out,
-     * the content should remain in the previous location, while after the switch it should
-     * be at the desired location.
-     */
-    private fun resolveLocationForFading(): Int {
-        if (isCrossFadeAnimatorRunning) {
-            // When animating between two hosts with a fade, let's keep ourselves in the old
-            // location for the first half, and then switch over to the end location
-            if (animationCrossFadeProgress > 0.5 || previousLocation == -1) {
-                return crossFadeAnimationEndLocation
-            } else {
-                return crossFadeAnimationStartLocation
-            }
-        }
-        return desiredLocation
-    }
-
-    private fun isTransitionRunning(): Boolean {
-        return isCurrentlyInGuidedTransformation() && getTransformationProgress() != 1.0f ||
-                animator.isRunning || animationPending
-    }
-
-    @MediaLocation
-    private fun calculateLocation(): Int {
-        if (blockLocationChanges) {
-            // Keep the current location until we're allowed to again
-            return desiredLocation
-        }
-        val onLockscreen = (!bypassController.bypassEnabled &&
-            (statusbarState == StatusBarState.KEYGUARD))
-        val location = when {
-            dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY
-            (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
-            qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
-            !hasActiveMedia -> LOCATION_QS
-            onLockscreen && isSplitShadeExpanding() -> LOCATION_QS
-            onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
-            onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN
-            else -> LOCATION_QQS
-        }
-        // When we're on lock screen and the player is not active, we should keep it in QS.
-        // Otherwise it will try to animate a transition that doesn't make sense.
-        if (location == LOCATION_LOCKSCREEN && getHost(location)?.visible != true &&
-            !statusBarStateController.isDozing) {
-            return LOCATION_QS
-        }
-        if (location == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QS &&
-            collapsingShadeFromQS) {
-            // When collapsing on the lockscreen, we want to remain in QS
-            return LOCATION_QS
-        }
-        if (location != LOCATION_LOCKSCREEN && desiredLocation == LOCATION_LOCKSCREEN &&
-            !fullyAwake) {
-            // When unlocking from dozing / while waking up, the media shouldn't be transitioning
-            // in an animated way. Let's keep it in the lockscreen until we're fully awake and
-            // reattach it without an animation
-            return LOCATION_LOCKSCREEN
-        }
-        if (skipQqsOnExpansion) {
-            // When doing an immediate expand or collapse, we want to keep it in QS.
-            return LOCATION_QS
-        }
-        return location
-    }
-
-    private fun isSplitShadeExpanding(): Boolean {
-        return inSplitShade && isTransitioningToFullShade
-    }
-
-    /**
-     * Are we currently transforming to the full shade and already in QQS
-     */
-    private fun isTransformingToFullShadeAndInQQS(): Boolean {
-        if (!isTransitioningToFullShade) {
-            return false
-        }
-        if (inSplitShade) {
-            // Split shade doesn't use QQS.
-            return false
-        }
-        return fullShadeTransitionProgress > 0.5f
-    }
-
-    /**
-     * Is the current transformationType fading
-     */
-    private fun isCurrentlyFading(): Boolean {
-        if (isSplitShadeExpanding()) {
-            // Split shade always uses transition instead of fade.
-            return false
-        }
-        if (isTransitioningToFullShade) {
-            return true
-        }
-        return isCrossFadeAnimatorRunning
-    }
-
-    /**
-     * Returns true when the media card could be visible to the user if existed.
-     */
-    private fun isVisibleToUser(): Boolean {
-        return isLockScreenVisibleToUser() || isLockScreenShadeVisibleToUser() ||
-                isHomeScreenShadeVisibleToUser()
-    }
-
-    private fun isLockScreenVisibleToUser(): Boolean {
-        return !statusBarStateController.isDozing &&
-                !keyguardViewController.isBouncerShowing &&
-                statusBarStateController.state == StatusBarState.KEYGUARD &&
-                allowMediaPlayerOnLockScreen &&
-                statusBarStateController.isExpanded &&
-                !qsExpanded
-    }
-
-    private fun isLockScreenShadeVisibleToUser(): Boolean {
-        return !statusBarStateController.isDozing &&
-                !keyguardViewController.isBouncerShowing &&
-                (statusBarStateController.state == StatusBarState.SHADE_LOCKED ||
-                        (statusBarStateController.state == StatusBarState.KEYGUARD && qsExpanded))
-    }
-
-    private fun isHomeScreenShadeVisibleToUser(): Boolean {
-        return !statusBarStateController.isDozing &&
-                statusBarStateController.state == StatusBarState.SHADE &&
-                statusBarStateController.isExpanded
-    }
-
-    companion object {
-        /**
-         * Attached in expanded quick settings
-         */
-        const val LOCATION_QS = 0
-
-        /**
-         * Attached in the collapsed QS
-         */
-        const val LOCATION_QQS = 1
-
-        /**
-         * Attached on the lock screen
-         */
-        const val LOCATION_LOCKSCREEN = 2
-
-        /**
-         * Attached on the dream overlay
-         */
-        const val LOCATION_DREAM_OVERLAY = 3
-
-        /**
-         * Attached at the root of the hierarchy in an overlay
-         */
-        const val IN_OVERLAY = -1000
-
-        /**
-         * The default transformation type where the hosts transform into each other using a direct
-         * transition
-         */
-        const val TRANSFORMATION_TYPE_TRANSITION = 0
-
-        /**
-         * A transformation type where content fades from one place to another instead of
-         * transitioning
-         */
-        const val TRANSFORMATION_TYPE_FADE = 1
-    }
-}
-private val EMPTY_RECT = Rect()
-
-@IntDef(prefix = ["TRANSFORMATION_TYPE_"], value = [
-    MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION,
-    MediaHierarchyManager.TRANSFORMATION_TYPE_FADE])
-@Retention(AnnotationRetention.SOURCE)
-private annotation class TransformationType
-
-@IntDef(prefix = ["LOCATION_"], value = [
-    MediaHierarchyManager.LOCATION_QS,
-    MediaHierarchyManager.LOCATION_QQS,
-    MediaHierarchyManager.LOCATION_LOCKSCREEN,
-    MediaHierarchyManager.LOCATION_DREAM_OVERLAY])
-@Retention(AnnotationRetention.SOURCE)
-annotation class MediaLocation
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt
deleted file mode 100644
index aea2934..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2020 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.media
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.util.animation.MeasurementOutput
-import com.android.systemui.util.traceSection
-import javax.inject.Inject
-
-/**
- * A class responsible for managing all media host states of the various host locations and
- * coordinating the heights among different players. This class can be used to get the most up to
- * date state for any location.
- */
-@SysUISingleton
-class MediaHostStatesManager @Inject constructor() {
-
-    private val callbacks: MutableSet<Callback> = mutableSetOf()
-    private val controllers: MutableSet<MediaViewController> = mutableSetOf()
-
-    /**
-     * The overall sizes of the carousel. This is needed to make sure all players in the carousel
-     * have equal size.
-     */
-    val carouselSizes: MutableMap<Int, MeasurementOutput> = mutableMapOf()
-
-    /**
-     * A map with all media states of all locations.
-     */
-    val mediaHostStates: MutableMap<Int, MediaHostState> = mutableMapOf()
-
-    /**
-     * Notify that a media state for a given location has changed. Should only be called from
-     * Media hosts themselves.
-     */
-    fun updateHostState(
-        @MediaLocation location: Int,
-        hostState: MediaHostState
-    ) = traceSection("MediaHostStatesManager#updateHostState") {
-        val currentState = mediaHostStates.get(location)
-        if (!hostState.equals(currentState)) {
-            val newState = hostState.copy()
-            mediaHostStates.put(location, newState)
-            updateCarouselDimensions(location, hostState)
-            // First update all the controllers to ensure they get the chance to measure
-            for (controller in controllers) {
-                controller.stateCallback.onHostStateChanged(location, newState)
-            }
-
-            // Then update all other callbacks which may depend on the controllers above
-            for (callback in callbacks) {
-                callback.onHostStateChanged(location, newState)
-            }
-        }
-    }
-
-    /**
-     * Get the dimensions of all players combined, which determines the overall height of the
-     * media carousel and the media hosts.
-     */
-    fun updateCarouselDimensions(
-        @MediaLocation location: Int,
-        hostState: MediaHostState
-    ): MeasurementOutput = traceSection("MediaHostStatesManager#updateCarouselDimensions") {
-        val result = MeasurementOutput(0, 0)
-        for (controller in controllers) {
-            val measurement = controller.getMeasurementsForState(hostState)
-            measurement?.let {
-                if (it.measuredHeight > result.measuredHeight) {
-                    result.measuredHeight = it.measuredHeight
-                }
-                if (it.measuredWidth > result.measuredWidth) {
-                    result.measuredWidth = it.measuredWidth
-                }
-            }
-        }
-        carouselSizes[location] = result
-        return result
-    }
-
-    /**
-     * Add a callback to be called when a MediaState has updated
-     */
-    fun addCallback(callback: Callback) {
-        callbacks.add(callback)
-    }
-
-    /**
-     * Remove a callback that listens to media states
-     */
-    fun removeCallback(callback: Callback) {
-        callbacks.remove(callback)
-    }
-
-    /**
-     * Register a controller that listens to media states and is used to determine the size of
-     * the media carousel
-     */
-    fun addController(controller: MediaViewController) {
-        controllers.add(controller)
-    }
-
-    /**
-     * Notify the manager about the removal of a controller.
-     */
-    fun removeController(controller: MediaViewController) {
-        controllers.remove(controller)
-    }
-
-    interface Callback {
-        /**
-         * Notify the callbacks that a media state for a host has changed, and that the
-         * corresponding view states should be updated and applied
-         */
-        fun onHostStateChanged(@MediaLocation location: Int, mediaHostState: MediaHostState)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 1ac2a07..be357ee 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -182,8 +182,7 @@
 
     override fun shouldGetOnlyDefaultActivities() = false
 
-    // TODO(b/240924732) flip the flag when the recents selector is ready
-    override fun shouldShowContentPreview() = false
+    override fun shouldShowContentPreview() = true
 
     override fun createContentPreviewView(parent: ViewGroup): ViewGroup =
         recentsViewController.createView(parent)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt b/packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt
deleted file mode 100644
index 00273bc..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt
+++ /dev/null
@@ -1,131 +0,0 @@
-package com.android.systemui.media
-
-import android.content.Context
-import android.os.SystemClock
-import android.util.AttributeSet
-import android.view.InputDevice
-import android.view.MotionEvent
-import android.view.ViewGroup
-import android.widget.HorizontalScrollView
-import com.android.systemui.Gefingerpoken
-import com.android.wm.shell.animation.physicsAnimator
-
-/**
- * A ScrollView used in Media that doesn't limit itself to the childs bounds. This is useful
- * when only measuring children but not the parent, when trying to apply a new scroll position
- */
-class MediaScrollView @JvmOverloads constructor(
-    context: Context,
-    attrs: AttributeSet? = null,
-    defStyleAttr: Int = 0
-)
-    : HorizontalScrollView(context, attrs, defStyleAttr) {
-
-    lateinit var contentContainer: ViewGroup
-        private set
-    var touchListener: Gefingerpoken? = null
-
-    /**
-     * The target value of the translation X animation. Only valid if the physicsAnimator is running
-     */
-    var animationTargetX = 0.0f
-
-    /**
-     * Get the current content translation. This is usually the normal translationX of the content,
-     * but when animating, it might differ
-     */
-    fun getContentTranslation() = if (contentContainer.physicsAnimator.isRunning()) {
-        animationTargetX
-    } else {
-        contentContainer.translationX
-    }
-
-    /**
-     * Convert between the absolute (left-to-right) and relative (start-to-end) scrollX of the media
-     * carousel.  The player indices are always relative (start-to-end) and the scrollView.scrollX
-     * is always absolute.  This function is its own inverse.
-     */
-    private fun transformScrollX(scrollX: Int): Int = if (isLayoutRtl) {
-        contentContainer.width - width - scrollX
-    } else {
-        scrollX
-    }
-
-    /**
-     * Get the layoutDirection-relative (start-to-end) scroll X position of the carousel.
-     */
-    var relativeScrollX: Int
-        get() = transformScrollX(scrollX)
-        set(value) {
-            scrollX = transformScrollX(value)
-        }
-
-    /**
-     * Allow all scrolls to go through, use base implementation
-     */
-    override fun scrollTo(x: Int, y: Int) {
-        if (mScrollX != x || mScrollY != y) {
-            val oldX: Int = mScrollX
-            val oldY: Int = mScrollY
-            mScrollX = x
-            mScrollY = y
-            invalidateParentCaches()
-            onScrollChanged(mScrollX, mScrollY, oldX, oldY)
-            if (!awakenScrollBars()) {
-                postInvalidateOnAnimation()
-            }
-        }
-    }
-
-    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
-        var intercept = false
-        touchListener?.let {
-            intercept = it.onInterceptTouchEvent(ev)
-        }
-        return super.onInterceptTouchEvent(ev) || intercept
-    }
-
-    override fun onTouchEvent(ev: MotionEvent?): Boolean {
-        var touch = false
-        touchListener?.let {
-            touch = it.onTouchEvent(ev)
-        }
-        return super.onTouchEvent(ev) || touch
-    }
-
-    override fun onFinishInflate() {
-        super.onFinishInflate()
-        contentContainer = getChildAt(0) as ViewGroup
-    }
-
-    override fun overScrollBy(
-        deltaX: Int,
-        deltaY: Int,
-        scrollX: Int,
-        scrollY: Int,
-        scrollRangeX: Int,
-        scrollRangeY: Int,
-        maxOverScrollX: Int,
-        maxOverScrollY: Int,
-        isTouchEvent: Boolean
-    ): Boolean {
-        if (getContentTranslation() != 0.0f) {
-            // When we're dismissing we ignore all the scrolling
-            return false
-        }
-        return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
-                scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent)
-    }
-
-    /**
-     * Cancel the current touch event going on.
-     */
-    fun cancelCurrentScroll() {
-        val now = SystemClock.uptimeMillis()
-        val event = MotionEvent.obtain(now, now,
-                MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0)
-        event.source = InputDevice.SOURCE_TOUCHSCREEN
-        super.onTouchEvent(event)
-        event.recycle()
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt
deleted file mode 100644
index d9c58c0..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * 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.media
-
-import android.media.session.PlaybackState
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.dagger.MediaTimeoutListenerLog
-import javax.inject.Inject
-
-private const val TAG = "MediaTimeout"
-
-/**
- * A buffered log for [MediaTimeoutListener] events
- */
-@SysUISingleton
-class MediaTimeoutLogger @Inject constructor(
-    @MediaTimeoutListenerLog private val buffer: LogBuffer
-) {
-    fun logReuseListener(key: String) = buffer.log(
-        TAG,
-        LogLevel.DEBUG,
-        {
-            str1 = key
-        },
-        {
-            "reuse listener: $str1"
-        }
-    )
-
-    fun logMigrateListener(oldKey: String?, newKey: String?, hadListener: Boolean) = buffer.log(
-        TAG,
-        LogLevel.DEBUG,
-        {
-            str1 = oldKey
-            str2 = newKey
-            bool1 = hadListener
-        },
-        {
-            "migrate from $str1 to $str2, had listener? $bool1"
-        }
-    )
-
-    fun logUpdateListener(key: String, wasPlaying: Boolean) = buffer.log(
-        TAG,
-        LogLevel.DEBUG,
-        {
-            str1 = key
-            bool1 = wasPlaying
-        },
-        {
-            "updating $str1, was playing? $bool1"
-        }
-    )
-
-    fun logDelayedUpdate(key: String) = buffer.log(
-        TAG,
-        LogLevel.DEBUG,
-        {
-            str1 = key
-        },
-        {
-            "deliver delayed playback state for $str1"
-        }
-    )
-
-    fun logSessionDestroyed(key: String) = buffer.log(
-        TAG,
-        LogLevel.DEBUG,
-        {
-            str1 = key
-        },
-        {
-            "session destroyed $str1"
-        }
-    )
-
-    fun logPlaybackState(key: String, state: PlaybackState?) = buffer.log(
-        TAG,
-        LogLevel.VERBOSE,
-        {
-            str1 = key
-            str2 = state?.toString()
-        },
-        {
-            "state update: key=$str1 state=$str2"
-        }
-    )
-
-    fun logStateCallback(key: String) = buffer.log(
-            TAG,
-            LogLevel.VERBOSE,
-            {
-                str1 = key
-            },
-            {
-                "dispatching state update for $key"
-            }
-    )
-
-    fun logScheduleTimeout(key: String, playing: Boolean, resumption: Boolean) = buffer.log(
-        TAG,
-        LogLevel.DEBUG,
-        {
-            str1 = key
-            bool1 = playing
-            bool2 = resumption
-        },
-        {
-            "schedule timeout $str1, playing=$bool1 resumption=$bool2"
-        }
-    )
-
-    fun logCancelIgnored(key: String) = buffer.log(
-        TAG,
-        LogLevel.DEBUG,
-        {
-            str1 = key
-        },
-        {
-            "cancellation already exists for $str1"
-        }
-    )
-
-    fun logTimeout(key: String) = buffer.log(
-        TAG,
-        LogLevel.DEBUG,
-        {
-            str1 = key
-        },
-        {
-            "execute timeout for $str1"
-        }
-    )
-
-    fun logTimeoutCancelled(key: String, reason: String) = buffer.log(
-        TAG,
-        LogLevel.VERBOSE,
-        {
-            str1 = key
-            str2 = reason
-        },
-        {
-            "media timeout cancelled for $str1, reason: $str2"
-        }
-    )
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
deleted file mode 100644
index faa7aae..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
+++ /dev/null
@@ -1,615 +0,0 @@
-/*
- * Copyright (C) 2020 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.media
-
-import android.content.Context
-import android.content.res.Configuration
-import androidx.annotation.VisibleForTesting
-import androidx.constraintlayout.widget.ConstraintSet
-import com.android.systemui.R
-import com.android.systemui.media.MediaCarouselController.Companion.CONTROLS_DELAY
-import com.android.systemui.media.MediaCarouselController.Companion.DETAILS_DELAY
-import com.android.systemui.media.MediaCarouselController.Companion.DURATION
-import com.android.systemui.media.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY
-import com.android.systemui.media.MediaCarouselController.Companion.MEDIATITLES_DELAY
-import com.android.systemui.media.MediaCarouselController.Companion.calculateAlpha
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.animation.MeasurementOutput
-import com.android.systemui.util.animation.TransitionLayout
-import com.android.systemui.util.animation.TransitionLayoutController
-import com.android.systemui.util.animation.TransitionViewState
-import com.android.systemui.util.traceSection
-import javax.inject.Inject
-
-/**
- * A class responsible for controlling a single instance of a media player handling interactions
- * with the view instance and keeping the media view states up to date.
- */
-class MediaViewController @Inject constructor(
-    private val context: Context,
-    private val configurationController: ConfigurationController,
-    private val mediaHostStatesManager: MediaHostStatesManager,
-    private val logger: MediaViewLogger
-) {
-
-    /**
-     * Indicating that the media view controller is for a notification-based player,
-     * session-based player, or recommendation
-     */
-    enum class TYPE {
-        PLAYER, RECOMMENDATION
-    }
-
-    companion object {
-        @JvmField
-        val GUTS_ANIMATION_DURATION = 500L
-        val controlIds = setOf(
-                R.id.media_progress_bar,
-                R.id.actionNext,
-                R.id.actionPrev,
-                R.id.action0,
-                R.id.action1,
-                R.id.action2,
-                R.id.action3,
-                R.id.action4,
-                R.id.media_scrubbing_elapsed_time,
-                R.id.media_scrubbing_total_time
-        )
-
-        val detailIds = setOf(
-                R.id.header_title,
-                R.id.header_artist,
-                R.id.actionPlayPause,
-        )
-    }
-
-    /**
-     * A listener when the current dimensions of the player change
-     */
-    lateinit var sizeChangedListener: () -> Unit
-    private var firstRefresh: Boolean = true
-    @VisibleForTesting
-    private var transitionLayout: TransitionLayout? = null
-    private val layoutController = TransitionLayoutController()
-    private var animationDelay: Long = 0
-    private var animationDuration: Long = 0
-    private var animateNextStateChange: Boolean = false
-    private val measurement = MeasurementOutput(0, 0)
-    private var type: TYPE = TYPE.PLAYER
-
-    /**
-     * A map containing all viewStates for all locations of this mediaState
-     */
-    private val viewStates: MutableMap<CacheKey, TransitionViewState?> = mutableMapOf()
-
-    /**
-     * The ending location of the view where it ends when all animations and transitions have
-     * finished
-     */
-    @MediaLocation
-    var currentEndLocation: Int = -1
-
-    /**
-     * The starting location of the view where it starts for all animations and transitions
-     */
-    @MediaLocation
-    private var currentStartLocation: Int = -1
-
-    /**
-     * The progress of the transition or 1.0 if there is no transition happening
-     */
-    private var currentTransitionProgress: Float = 1.0f
-
-    /**
-     * A temporary state used to store intermediate measurements.
-     */
-    private val tmpState = TransitionViewState()
-
-    /**
-     * A temporary state used to store intermediate measurements.
-     */
-    private val tmpState2 = TransitionViewState()
-
-    /**
-     * A temporary state used to store intermediate measurements.
-     */
-    private val tmpState3 = TransitionViewState()
-
-    /**
-     * A temporary cache key to be used to look up cache entries
-     */
-    private val tmpKey = CacheKey()
-
-    /**
-     * The current width of the player. This might not factor in case the player is animating
-     * to the current state, but represents the end state
-     */
-    var currentWidth: Int = 0
-    /**
-     * The current height of the player. This might not factor in case the player is animating
-     * to the current state, but represents the end state
-     */
-    var currentHeight: Int = 0
-
-    /**
-     * Get the translationX of the layout
-     */
-    var translationX: Float = 0.0f
-        private set
-        get() {
-            return transitionLayout?.translationX ?: 0.0f
-        }
-
-    /**
-     * Get the translationY of the layout
-     */
-    var translationY: Float = 0.0f
-        private set
-        get() {
-            return transitionLayout?.translationY ?: 0.0f
-        }
-
-    /**
-     * A callback for RTL config changes
-     */
-    private val configurationListener = object : ConfigurationController.ConfigurationListener {
-        override fun onConfigChanged(newConfig: Configuration?) {
-            // Because the TransitionLayout is not always attached (and calculates/caches layout
-            // results regardless of attach state), we have to force the layoutDirection of the view
-            // to the correct value for the user's current locale to ensure correct recalculation
-            // when/after calling refreshState()
-            newConfig?.apply {
-                if (transitionLayout?.rawLayoutDirection != layoutDirection) {
-                    transitionLayout?.layoutDirection = layoutDirection
-                    refreshState()
-                }
-            }
-        }
-    }
-
-    /**
-     * A callback for media state changes
-     */
-    val stateCallback = object : MediaHostStatesManager.Callback {
-        override fun onHostStateChanged(
-            @MediaLocation location: Int,
-            mediaHostState: MediaHostState
-        ) {
-            if (location == currentEndLocation || location == currentStartLocation) {
-                setCurrentState(currentStartLocation,
-                        currentEndLocation,
-                        currentTransitionProgress,
-                        applyImmediately = false)
-            }
-        }
-    }
-
-    /**
-     * The expanded constraint set used to render a expanded player. If it is modified, make sure
-     * to call [refreshState]
-     */
-    val collapsedLayout = ConstraintSet()
-
-    /**
-     * The expanded constraint set used to render a collapsed player. If it is modified, make sure
-     * to call [refreshState]
-     */
-    val expandedLayout = ConstraintSet()
-
-    /**
-     * Whether the guts are visible for the associated player.
-     */
-    var isGutsVisible = false
-        private set
-
-    init {
-        mediaHostStatesManager.addController(this)
-        layoutController.sizeChangedListener = { width: Int, height: Int ->
-            currentWidth = width
-            currentHeight = height
-            sizeChangedListener.invoke()
-        }
-        configurationController.addCallback(configurationListener)
-    }
-
-    /**
-     * Notify this controller that the view has been removed and all listeners should be destroyed
-     */
-    fun onDestroy() {
-        mediaHostStatesManager.removeController(this)
-        configurationController.removeCallback(configurationListener)
-    }
-
-    /**
-     * Show guts with an animated transition.
-     */
-    fun openGuts() {
-        if (isGutsVisible) return
-        isGutsVisible = true
-        animatePendingStateChange(GUTS_ANIMATION_DURATION, 0L)
-        setCurrentState(currentStartLocation,
-                currentEndLocation,
-                currentTransitionProgress,
-                applyImmediately = false)
-    }
-
-    /**
-     * Close the guts for the associated player.
-     *
-     * @param immediate if `false`, it will animate the transition.
-     */
-    @JvmOverloads
-    fun closeGuts(immediate: Boolean = false) {
-        if (!isGutsVisible) return
-        isGutsVisible = false
-        if (!immediate) {
-            animatePendingStateChange(GUTS_ANIMATION_DURATION, 0L)
-        }
-        setCurrentState(currentStartLocation,
-                currentEndLocation,
-                currentTransitionProgress,
-                applyImmediately = immediate)
-    }
-
-    private fun ensureAllMeasurements() {
-        val mediaStates = mediaHostStatesManager.mediaHostStates
-        for (entry in mediaStates) {
-            obtainViewState(entry.value)
-        }
-    }
-
-    /**
-     * Get the constraintSet for a given expansion
-     */
-    private fun constraintSetForExpansion(expansion: Float): ConstraintSet =
-            if (expansion > 0) expandedLayout else collapsedLayout
-
-    /**
-     * Set the views to be showing/hidden based on the [isGutsVisible] for a given
-     * [TransitionViewState].
-     */
-    private fun setGutsViewState(viewState: TransitionViewState) {
-        val controlsIds = when (type) {
-            TYPE.PLAYER -> MediaViewHolder.controlsIds
-            TYPE.RECOMMENDATION -> RecommendationViewHolder.controlsIds
-        }
-        val gutsIds = GutsViewHolder.ids
-        controlsIds.forEach { id ->
-            viewState.widgetStates.get(id)?.let { state ->
-                // Make sure to use the unmodified state if guts are not visible.
-                state.alpha = if (isGutsVisible) 0f else state.alpha
-                state.gone = if (isGutsVisible) true else state.gone
-            }
-        }
-        gutsIds.forEach { id ->
-            viewState.widgetStates.get(id)?.let { state ->
-                // Make sure to use the unmodified state if guts are visible
-                state.alpha = if (isGutsVisible) state.alpha else 0f
-                state.gone = if (isGutsVisible) state.gone else true
-            }
-        }
-    }
-
-    /**
-     * Apply squishFraction to a copy of viewState such that the cached version is untouched.
-    */
-    internal fun squishViewState(
-        viewState: TransitionViewState,
-        squishFraction: Float
-    ): TransitionViewState {
-        val squishedViewState = viewState.copy()
-        squishedViewState.height = (squishedViewState.height * squishFraction).toInt()
-        controlIds.forEach { id ->
-            squishedViewState.widgetStates.get(id)?.let { state ->
-                state.alpha = calculateAlpha(squishFraction, CONTROLS_DELAY, DURATION)
-            }
-        }
-
-        detailIds.forEach { id ->
-            squishedViewState.widgetStates.get(id)?.let { state ->
-                state.alpha = calculateAlpha(squishFraction, DETAILS_DELAY, DURATION)
-            }
-        }
-
-        RecommendationViewHolder.mediaContainersIds.forEach { id ->
-            squishedViewState.widgetStates.get(id)?.let { state ->
-                state.alpha = calculateAlpha(squishFraction, MEDIACONTAINERS_DELAY, DURATION)
-            }
-        }
-
-        RecommendationViewHolder.mediaTitlesAndSubtitlesIds.forEach { id ->
-            squishedViewState.widgetStates.get(id)?.let { state ->
-                state.alpha = calculateAlpha(squishFraction, MEDIATITLES_DELAY, DURATION)
-            }
-        }
-
-        return squishedViewState
-    }
-
-    /**
-     * Obtain a new viewState for a given media state. This usually returns a cached state, but if
-     * it's not available, it will recreate one by measuring, which may be expensive.
-     */
-     @VisibleForTesting
-     fun obtainViewState(state: MediaHostState?): TransitionViewState? {
-        if (state == null || state.measurementInput == null) {
-            return null
-        }
-        // Only a subset of the state is relevant to get a valid viewState. Let's get the cachekey
-        var cacheKey = getKey(state, isGutsVisible, tmpKey)
-        val viewState = viewStates[cacheKey]
-        if (viewState != null) {
-            // we already have cached this measurement, let's continue
-            if (state.squishFraction <= 1f) {
-                return squishViewState(viewState, state.squishFraction)
-            }
-            return viewState
-        }
-        // Copy the key since this might call recursively into it and we're using tmpKey
-        cacheKey = cacheKey.copy()
-        val result: TransitionViewState?
-
-        if (transitionLayout == null) {
-            return null
-        }
-        // Let's create a new measurement
-        if (state.expansion == 0.0f || state.expansion == 1.0f) {
-            result = transitionLayout!!.calculateViewState(
-                    state.measurementInput!!,
-                    constraintSetForExpansion(state.expansion),
-                    TransitionViewState())
-
-            setGutsViewState(result)
-            // We don't want to cache interpolated or null states as this could quickly fill up
-            // our cache. We only cache the start and the end states since the interpolation
-            // is cheap
-            viewStates[cacheKey] = result
-        } else {
-            // This is an interpolated state
-            val startState = state.copy().also { it.expansion = 0.0f }
-
-            // Given that we have a measurement and a view, let's get (guaranteed) viewstates
-            // from the start and end state and interpolate them
-            val startViewState = obtainViewState(startState) as TransitionViewState
-            val endState = state.copy().also { it.expansion = 1.0f }
-            val endViewState = obtainViewState(endState) as TransitionViewState
-            result = layoutController.getInterpolatedState(
-                    startViewState,
-                    endViewState,
-                    state.expansion)
-        }
-        if (state.squishFraction <= 1f) {
-            return squishViewState(result, state.squishFraction)
-        }
-        return result
-    }
-
-    private fun getKey(
-        state: MediaHostState,
-        guts: Boolean,
-        result: CacheKey
-    ): CacheKey {
-        result.apply {
-            heightMeasureSpec = state.measurementInput?.heightMeasureSpec ?: 0
-            widthMeasureSpec = state.measurementInput?.widthMeasureSpec ?: 0
-            expansion = state.expansion
-            gutsVisible = guts
-        }
-        return result
-    }
-
-    /**
-     * Attach a view to this controller. This may perform measurements if it's not available yet
-     * and should therefore be done carefully.
-     */
-    fun attach(
-        transitionLayout: TransitionLayout,
-        type: TYPE
-    ) = traceSection("MediaViewController#attach") {
-        updateMediaViewControllerType(type)
-        logger.logMediaLocation("attach $type", currentStartLocation, currentEndLocation)
-        this.transitionLayout = transitionLayout
-        layoutController.attach(transitionLayout)
-        if (currentEndLocation == -1) {
-            return
-        }
-        // Set the previously set state immediately to the view, now that it's finally attached
-        setCurrentState(
-                startLocation = currentStartLocation,
-                endLocation = currentEndLocation,
-                transitionProgress = currentTransitionProgress,
-                applyImmediately = true)
-    }
-
-    /**
-     * Obtain a measurement for a given location. This makes sure that the state is up to date
-     * and all widgets know their location. Calling this method may create a measurement if we
-     * don't have a cached value available already.
-     */
-    fun getMeasurementsForState(
-        hostState: MediaHostState
-    ): MeasurementOutput? = traceSection("MediaViewController#getMeasurementsForState") {
-        val viewState = obtainViewState(hostState) ?: return null
-        measurement.measuredWidth = viewState.width
-        measurement.measuredHeight = viewState.height
-        return measurement
-    }
-
-    /**
-     * Set a new state for the controlled view which can be an interpolation between multiple
-     * locations.
-     */
-    fun setCurrentState(
-        @MediaLocation startLocation: Int,
-        @MediaLocation endLocation: Int,
-        transitionProgress: Float,
-        applyImmediately: Boolean
-    ) = traceSection("MediaViewController#setCurrentState") {
-        currentEndLocation = endLocation
-        currentStartLocation = startLocation
-        currentTransitionProgress = transitionProgress
-        logger.logMediaLocation("setCurrentState", startLocation, endLocation)
-
-        val shouldAnimate = animateNextStateChange && !applyImmediately
-
-        val endHostState = mediaHostStatesManager.mediaHostStates[endLocation] ?: return
-        val startHostState = mediaHostStatesManager.mediaHostStates[startLocation]
-
-        // Obtain the view state that we'd want to be at the end
-        // The view might not be bound yet or has never been measured and in that case will be
-        // reset once the state is fully available
-        var endViewState = obtainViewState(endHostState) ?: return
-        endViewState = updateViewStateToCarouselSize(endViewState, endLocation, tmpState2)!!
-        layoutController.setMeasureState(endViewState)
-
-        // If the view isn't bound, we can drop the animation, otherwise we'll execute it
-        animateNextStateChange = false
-        if (transitionLayout == null) {
-            return
-        }
-
-        val result: TransitionViewState
-        var startViewState = obtainViewState(startHostState)
-        startViewState = updateViewStateToCarouselSize(startViewState, startLocation, tmpState3)
-
-        if (!endHostState.visible) {
-            // Let's handle the case where the end is gone first. In this case we take the
-            // start viewState and will make it gone
-            if (startViewState == null || startHostState == null || !startHostState.visible) {
-                // the start isn't a valid state, let's use the endstate directly
-                result = endViewState
-            } else {
-                // Let's get the gone presentation from the start state
-                result = layoutController.getGoneState(startViewState,
-                        startHostState.disappearParameters,
-                        transitionProgress,
-                        tmpState)
-            }
-        } else if (startHostState != null && !startHostState.visible) {
-            // We have a start state and it is gone.
-            // Let's get presentation from the endState
-            result = layoutController.getGoneState(endViewState, endHostState.disappearParameters,
-                    1.0f - transitionProgress,
-                    tmpState)
-        } else if (transitionProgress == 1.0f || startViewState == null) {
-            // We're at the end. Let's use that state
-            result = endViewState
-        } else if (transitionProgress == 0.0f) {
-            // We're at the start. Let's use that state
-            result = startViewState
-        } else {
-            result = layoutController.getInterpolatedState(startViewState, endViewState,
-                    transitionProgress, tmpState)
-        }
-        logger.logMediaSize("setCurrentState", result.width, result.height)
-        layoutController.setState(result, applyImmediately, shouldAnimate, animationDuration,
-                animationDelay)
-    }
-
-    private fun updateViewStateToCarouselSize(
-        viewState: TransitionViewState?,
-        location: Int,
-        outState: TransitionViewState
-    ): TransitionViewState? {
-        val result = viewState?.copy(outState) ?: return null
-        val overrideSize = mediaHostStatesManager.carouselSizes[location]
-        overrideSize?.let {
-            // To be safe we're using a maximum here. The override size should always be set
-            // properly though.
-            result.height = Math.max(it.measuredHeight, result.height)
-            result.width = Math.max(it.measuredWidth, result.width)
-        }
-        logger.logMediaSize("update to carousel", result.width, result.height)
-        return result
-    }
-
-    private fun updateMediaViewControllerType(type: TYPE) {
-        this.type = type
-
-        // These XML resources contain ConstraintSets that will apply to this player type's layout
-        when (type) {
-            TYPE.PLAYER -> {
-                collapsedLayout.load(context, R.xml.media_session_collapsed)
-                expandedLayout.load(context, R.xml.media_session_expanded)
-            }
-            TYPE.RECOMMENDATION -> {
-                collapsedLayout.load(context, R.xml.media_recommendation_collapsed)
-                expandedLayout.load(context, R.xml.media_recommendation_expanded)
-            }
-        }
-        refreshState()
-    }
-
-    /**
-     * Retrieves the [TransitionViewState] and [MediaHostState] of a [@MediaLocation].
-     * In the event of [location] not being visible, [locationWhenHidden] will be used instead.
-     *
-     * @param location Target
-     * @param locationWhenHidden Location that will be used when the target is not
-     * [MediaHost.visible]
-     * @return State require for executing a transition, and also the respective [MediaHost].
-     */
-    private fun obtainViewStateForLocation(@MediaLocation location: Int): TransitionViewState? {
-        val mediaHostState = mediaHostStatesManager.mediaHostStates[location] ?: return null
-        return obtainViewState(mediaHostState)
-    }
-
-    /**
-     * Notify that the location is changing right now and a [setCurrentState] change is imminent.
-     * This updates the width the view will me measured with.
-     */
-    fun onLocationPreChange(@MediaLocation newLocation: Int) {
-        obtainViewStateForLocation(newLocation)?.let {
-            layoutController.setMeasureState(it)
-        }
-    }
-
-    /**
-     * Request that the next state change should be animated with the given parameters.
-     */
-    fun animatePendingStateChange(duration: Long, delay: Long) {
-        animateNextStateChange = true
-        animationDuration = duration
-        animationDelay = delay
-    }
-
-    /**
-     * Clear all existing measurements and refresh the state to match the view.
-     */
-    fun refreshState() = traceSection("MediaViewController#refreshState") {
-        // Let's clear all of our measurements and recreate them!
-        viewStates.clear()
-        if (firstRefresh) {
-            // This is the first bind, let's ensure we pre-cache all measurements. Otherwise
-            // We'll just load these on demand.
-            ensureAllMeasurements()
-            firstRefresh = false
-        }
-        setCurrentState(currentStartLocation, currentEndLocation, currentTransitionProgress,
-                applyImmediately = true)
-    }
-}
-
-/**
- * An internal key for the cache of mediaViewStates. This is a subset of the full host state.
- */
-private data class CacheKey(
-    var widthMeasureSpec: Int = -1,
-    var heightMeasureSpec: Int = -1,
-    var expansion: Float = 0.0f,
-    var gutsVisible: Boolean = false
-)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt
deleted file mode 100644
index 73868189..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.media
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.dagger.MediaViewLog
-import javax.inject.Inject
-
-private const val TAG = "MediaView"
-
-/**
- * A buffered log for media view events that are too noisy for regular logging
- */
-@SysUISingleton
-class MediaViewLogger @Inject constructor(
-    @MediaViewLog private val buffer: LogBuffer
-) {
-    fun logMediaSize(reason: String, width: Int, height: Int) {
-        buffer.log(
-                TAG,
-                LogLevel.DEBUG,
-                {
-                    str1 = reason
-                    int1 = width
-                    int2 = height
-                },
-                {
-                    "size ($str1): $int1 x $int2"
-                }
-        )
-    }
-
-    fun logMediaLocation(reason: String, startLocation: Int, endLocation: Int) {
-        buffer.log(
-                TAG,
-                LogLevel.DEBUG,
-                {
-                    str1 = reason
-                    int1 = startLocation
-                    int2 = endLocation
-                },
-                {
-                    "location ($str1): $int1 -> $int2"
-                }
-        )
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt
deleted file mode 100644
index 8ae75fc..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2020 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.media
-
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.TextView
-import com.android.systemui.R
-import com.android.systemui.util.animation.TransitionLayout
-
-private const val TAG = "RecommendationViewHolder"
-
-/** ViewHolder for a Smartspace media recommendation. */
-class RecommendationViewHolder private constructor(itemView: View) {
-
-    val recommendations = itemView as TransitionLayout
-
-    // Recommendation screen
-    val cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
-    val mediaCoverItems = listOf<ImageView>(
-        itemView.requireViewById(R.id.media_cover1),
-        itemView.requireViewById(R.id.media_cover2),
-        itemView.requireViewById(R.id.media_cover3)
-    )
-    val mediaCoverContainers = listOf<ViewGroup>(
-        itemView.requireViewById(R.id.media_cover1_container),
-        itemView.requireViewById(R.id.media_cover2_container),
-        itemView.requireViewById(R.id.media_cover3_container)
-    )
-    val mediaTitles: List<TextView> = listOf(
-        itemView.requireViewById(R.id.media_title1),
-        itemView.requireViewById(R.id.media_title2),
-        itemView.requireViewById(R.id.media_title3)
-    )
-    val mediaSubtitles: List<TextView> = listOf(
-        itemView.requireViewById(R.id.media_subtitle1),
-        itemView.requireViewById(R.id.media_subtitle2),
-        itemView.requireViewById(R.id.media_subtitle3)
-    )
-
-    val gutsViewHolder = GutsViewHolder(itemView)
-
-    init {
-        (recommendations.background as IlluminationDrawable).let { background ->
-            mediaCoverContainers.forEach { background.registerLightSource(it) }
-            background.registerLightSource(gutsViewHolder.cancel)
-            background.registerLightSource(gutsViewHolder.dismiss)
-            background.registerLightSource(gutsViewHolder.settings)
-        }
-    }
-
-    fun marquee(start: Boolean, delay: Long) {
-        gutsViewHolder.marquee(start, delay, TAG)
-    }
-
-    companion object {
-        /**
-         * Creates a RecommendationViewHolder.
-         *
-         * @param inflater LayoutInflater to use to inflate the layout.
-         * @param parent Parent of inflated view.
-         */
-        @JvmStatic fun create(inflater: LayoutInflater, parent: ViewGroup):
-            RecommendationViewHolder {
-            val itemView =
-                inflater.inflate(
-                    R.layout.media_smartspace_recommendations,
-                    parent,
-                    false /* attachToRoot */)
-            // Because this media view (a TransitionLayout) is used to measure and layout the views
-            // in various states before being attached to its parent, we can't depend on the default
-            // LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction.
-            itemView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
-            return RecommendationViewHolder(itemView)
-        }
-
-        // Res Ids for the control components on the recommendation view.
-        val controlsIds = setOf(
-            R.id.recommendation_card_icon,
-            R.id.media_cover1,
-            R.id.media_cover2,
-            R.id.media_cover3,
-            R.id.media_cover1_container,
-            R.id.media_cover2_container,
-            R.id.media_cover3_container,
-            R.id.media_title1,
-            R.id.media_title2,
-            R.id.media_title3,
-            R.id.media_subtitle1,
-            R.id.media_subtitle2,
-            R.id.media_subtitle3
-        )
-
-        val mediaTitlesAndSubtitlesIds = setOf(
-            R.id.media_title1,
-            R.id.media_title2,
-            R.id.media_title3,
-            R.id.media_subtitle1,
-            R.id.media_subtitle2,
-            R.id.media_subtitle3
-        )
-
-        val mediaContainersIds = setOf(
-            R.id.media_cover1_container,
-            R.id.media_cover2_container,
-            R.id.media_cover3_container
-        )
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt
deleted file mode 100644
index 41f7354..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.media
-
-import android.content.ComponentName
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.dagger.MediaBrowserLog
-import javax.inject.Inject
-
-/** A logger for events in [ResumeMediaBrowser]. */
-@SysUISingleton
-class ResumeMediaBrowserLogger @Inject constructor(
-    @MediaBrowserLog private val buffer: LogBuffer
-) {
-    /** Logs that we've initiated a connection to a [android.media.browse.MediaBrowser]. */
-    fun logConnection(componentName: ComponentName, reason: String) = buffer.log(
-        TAG,
-        LogLevel.DEBUG,
-        {
-            str1 = componentName.toShortString()
-            str2 = reason
-        },
-        { "Connecting browser for component $str1 due to $str2" }
-    )
-
-    /** Logs that we've disconnected from a [android.media.browse.MediaBrowser]. */
-    fun logDisconnect(componentName: ComponentName) = buffer.log(
-        TAG,
-        LogLevel.DEBUG,
-        {
-            str1 = componentName.toShortString()
-        },
-        { "Disconnecting browser for component $str1" }
-    )
-
-    /**
-     * Logs that we received a [android.media.session.MediaController.Callback.onSessionDestroyed]
-     * event.
-     *
-     * @param isBrowserConnected true if there's a currently connected
-     *     [android.media.browse.MediaBrowser] and false otherwise.
-     * @param componentName the component name for the [ResumeMediaBrowser] that triggered this log.
-     */
-    fun logSessionDestroyed(
-        isBrowserConnected: Boolean,
-        componentName: ComponentName
-    ) = buffer.log(
-        TAG,
-        LogLevel.DEBUG,
-        {
-            bool1 = isBrowserConnected
-            str1 = componentName.toShortString()
-        },
-        { "Session destroyed. Active browser = $bool1. Browser component = $str1." }
-    )
-}
-
-private const val TAG = "MediaBrowser"
diff --git a/packages/SystemUI/src/com/android/systemui/media/GutsViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/GutsViewHolder.kt
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/media/GutsViewHolder.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/models/GutsViewHolder.kt
index 73240b5..5315067 100644
--- a/packages/SystemUI/src/com/android/systemui/media/GutsViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/GutsViewHolder.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.models
 
 import android.content.res.ColorStateList
 import android.util.Log
@@ -23,6 +23,9 @@
 import android.widget.ImageButton
 import android.widget.TextView
 import com.android.systemui.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.monet.ColorScheme
 
 /**
@@ -95,11 +98,6 @@
     }
 
     companion object {
-        val ids = setOf(
-            R.id.remove_text,
-            R.id.cancel,
-            R.id.dismiss,
-            R.id.settings
-        )
+        val ids = setOf(R.id.remove_text, R.id.cancel, R.id.dismiss, R.id.settings)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
similarity index 67%
rename from packages/SystemUI/src/com/android/systemui/media/MediaData.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
index 5b2cda0..f006442 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
@@ -14,7 +14,7 @@
  * limitations under the License
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.models.player
 
 import android.app.PendingIntent
 import android.graphics.drawable.Drawable
@@ -27,69 +27,42 @@
 data class MediaData(
     val userId: Int,
     val initialized: Boolean = false,
-    /**
-     * App name that will be displayed on the player.
-     */
+    /** App name that will be displayed on the player. */
     val app: String?,
-    /**
-     * App icon shown on player.
-     */
+    /** App icon shown on player. */
     val appIcon: Icon?,
-    /**
-     * Artist name.
-     */
+    /** Artist name. */
     val artist: CharSequence?,
-    /**
-     * Song name.
-     */
+    /** Song name. */
     val song: CharSequence?,
-    /**
-     * Album artwork.
-     */
+    /** Album artwork. */
     val artwork: Icon?,
-    /**
-     * List of generic action buttons for the media player, based on notification actions
-     */
+    /** List of generic action buttons for the media player, based on notification actions */
     val actions: List<MediaAction>,
-    /**
-     * Same as above, but shown on smaller versions of the player, like in QQS or keyguard.
-     */
+    /** Same as above, but shown on smaller versions of the player, like in QQS or keyguard. */
     val actionsToShowInCompact: List<Int>,
     /**
-     * Semantic actions buttons, based on the PlaybackState of the media session.
-     * If present, these actions will be preferred in the UI over [actions]
+     * Semantic actions buttons, based on the PlaybackState of the media session. If present, these
+     * actions will be preferred in the UI over [actions]
      */
     val semanticActions: MediaButton? = null,
-    /**
-     * Package name of the app that's posting the media.
-     */
+    /** Package name of the app that's posting the media. */
     val packageName: String,
-    /**
-     * Unique media session identifier.
-     */
+    /** Unique media session identifier. */
     val token: MediaSession.Token?,
-    /**
-     * Action to perform when the player is tapped.
-     * This is unrelated to {@link #actions}.
-     */
+    /** Action to perform when the player is tapped. This is unrelated to {@link #actions}. */
     val clickIntent: PendingIntent?,
-    /**
-     * Where the media is playing: phone, headphones, ear buds, remote session.
-     */
+    /** Where the media is playing: phone, headphones, ear buds, remote session. */
     val device: MediaDeviceData?,
     /**
-     * When active, a player will be displayed on keyguard and quick-quick settings.
-     * This is unrelated to the stream being playing or not, a player will not be active if
-     * timed out, or in resumption mode.
+     * When active, a player will be displayed on keyguard and quick-quick settings. This is
+     * unrelated to the stream being playing or not, a player will not be active if timed out, or in
+     * resumption mode.
      */
     var active: Boolean,
-    /**
-     * Action that should be performed to restart a non active session.
-     */
+    /** Action that should be performed to restart a non active session. */
     var resumeAction: Runnable?,
-    /**
-     * Playback location: one of PLAYBACK_LOCAL, PLAYBACK_CAST_LOCAL, or PLAYBACK_CAST_REMOTE
-     */
+    /** Playback location: one of PLAYBACK_LOCAL, PLAYBACK_CAST_LOCAL, or PLAYBACK_CAST_REMOTE */
     var playbackLocation: Int = PLAYBACK_LOCAL,
     /**
      * Indicates that this player is a resumption player (ie. It only shows a play actions which
@@ -102,29 +75,19 @@
     val notificationKey: String? = null,
     var hasCheckedForResume: Boolean = false,
 
-    /**
-     * If apps do not report PlaybackState, set as null to imply 'undetermined'
-     */
+    /** If apps do not report PlaybackState, set as null to imply 'undetermined' */
     val isPlaying: Boolean? = null,
 
-    /**
-     * Set from the notification and used as fallback when PlaybackState cannot be determined
-     */
+    /** Set from the notification and used as fallback when PlaybackState cannot be determined */
     val isClearable: Boolean = true,
 
-    /**
-     * Timestamp when this player was last active.
-     */
+    /** Timestamp when this player was last active. */
     var lastActive: Long = 0L,
 
-    /**
-     * Instance ID for logging purposes
-     */
+    /** Instance ID for logging purposes */
     val instanceId: InstanceId,
 
-    /**
-     * The UID of the app, used for logging
-     */
+    /** The UID of the app, used for logging */
     val appUid: Int
 ) {
     companion object {
@@ -141,37 +104,21 @@
     }
 }
 
-/**
- * Contains [MediaAction] objects which represent specific buttons in the UI
- */
+/** Contains [MediaAction] objects which represent specific buttons in the UI */
 data class MediaButton(
-    /**
-     * Play/pause button
-     */
+    /** Play/pause button */
     val playOrPause: MediaAction? = null,
-    /**
-     * Next button, or custom action
-     */
+    /** Next button, or custom action */
     val nextOrCustom: MediaAction? = null,
-    /**
-     * Previous button, or custom action
-     */
+    /** Previous button, or custom action */
     val prevOrCustom: MediaAction? = null,
-    /**
-     * First custom action space
-     */
+    /** First custom action space */
     val custom0: MediaAction? = null,
-    /**
-     * Second custom action space
-     */
+    /** Second custom action space */
     val custom1: MediaAction? = null,
-    /**
-     * Whether to reserve the empty space when the nextOrCustom is null
-     */
+    /** Whether to reserve the empty space when the nextOrCustom is null */
     val reserveNext: Boolean = false,
-    /**
-     * Whether to reserve the empty space when the prevOrCustom is null
-     */
+    /** Whether to reserve the empty space when the prevOrCustom is null */
     val reservePrev: Boolean = false
 ) {
     fun getActionById(id: Int): MediaAction? {
@@ -201,7 +148,8 @@
 
 /** State of the media device. */
 data class MediaDeviceData
-@JvmOverloads constructor(
+@JvmOverloads
+constructor(
     /** Whether or not to enable the chip */
     val enabled: Boolean,
 
@@ -221,8 +169,8 @@
     val showBroadcastButton: Boolean
 ) {
     /**
-     * Check whether [MediaDeviceData] objects are equal in all fields except the icon. The icon
-     * is ignored because it can change by reference frequently depending on the device type's
+     * Check whether [MediaDeviceData] objects are equal in all fields except the icon. The icon is
+     * ignored because it can change by reference frequently depending on the device type's
      * implementation, but this is not usually relevant unless other info has changed
      */
     fun equalsWithoutIcon(other: MediaDeviceData?): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
similarity index 82%
rename from packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
index fc9515c..2511324 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.models.player
 
 import android.view.LayoutInflater
 import android.view.View
@@ -25,13 +25,12 @@
 import android.widget.TextView
 import androidx.constraintlayout.widget.Barrier
 import com.android.systemui.R
+import com.android.systemui.media.controls.models.GutsViewHolder
 import com.android.systemui.util.animation.TransitionLayout
 
 private const val TAG = "MediaViewHolder"
 
-/**
- * Holder class for media player view
- */
+/** Holder class for media player view */
 class MediaViewHolder constructor(itemView: View) {
     val player = itemView as TransitionLayout
 
@@ -52,8 +51,7 @@
     // These views are only shown while the user is actively scrubbing
     val scrubbingElapsedTimeView: TextView =
         itemView.requireViewById(R.id.media_scrubbing_elapsed_time)
-    val scrubbingTotalTimeView: TextView =
-        itemView.requireViewById(R.id.media_scrubbing_total_time)
+    val scrubbingTotalTimeView: TextView = itemView.requireViewById(R.id.media_scrubbing_total_time)
 
     val gutsViewHolder = GutsViewHolder(itemView)
 
@@ -86,15 +84,7 @@
     }
 
     fun getTransparentActionButtons(): List<ImageButton> {
-        return listOf(
-                actionNext,
-                actionPrev,
-                action0,
-                action1,
-                action2,
-                action3,
-                action4
-        )
+        return listOf(actionNext, actionPrev, action0, action1, action2, action3, action4)
     }
 
     fun marquee(start: Boolean, delay: Long) {
@@ -108,10 +98,8 @@
          * @param inflater LayoutInflater to use to inflate the layout.
          * @param parent Parent of inflated view.
          */
-        @JvmStatic fun create(
-            inflater: LayoutInflater,
-            parent: ViewGroup
-        ): MediaViewHolder {
+        @JvmStatic
+        fun create(inflater: LayoutInflater, parent: ViewGroup): MediaViewHolder {
             val mediaView = inflater.inflate(R.layout.media_session_view, parent, false)
             mediaView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
             // Because this media view (a TransitionLayout) is used to measure and layout the views
@@ -124,7 +112,8 @@
             }
         }
 
-        val controlsIds = setOf(
+        val controlsIds =
+            setOf(
                 R.id.icon,
                 R.id.app_name,
                 R.id.header_title,
@@ -142,27 +131,23 @@
                 R.id.icon,
                 R.id.media_scrubbing_elapsed_time,
                 R.id.media_scrubbing_total_time
-        )
+            )
 
         // Buttons used for notification-based actions
-        val genericButtonIds = setOf(
-            R.id.action0,
-            R.id.action1,
-            R.id.action2,
-            R.id.action3,
-            R.id.action4
-        )
+        val genericButtonIds =
+            setOf(R.id.action0, R.id.action1, R.id.action2, R.id.action3, R.id.action4)
 
-        val expandedBottomActionIds = setOf(
-            R.id.actionPrev,
-            R.id.actionNext,
-            R.id.action0,
-            R.id.action1,
-            R.id.action2,
-            R.id.action3,
-            R.id.action4,
-            R.id.media_scrubbing_elapsed_time,
-            R.id.media_scrubbing_total_time
-        )
+        val expandedBottomActionIds =
+            setOf(
+                R.id.actionPrev,
+                R.id.actionNext,
+                R.id.action0,
+                R.id.action1,
+                R.id.action2,
+                R.id.action3,
+                R.id.action4,
+                R.id.media_scrubbing_elapsed_time,
+                R.id.media_scrubbing_total_time
+            )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
similarity index 68%
rename from packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
index 121021f..37d956b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.models.player
 
 import android.animation.Animator
 import android.animation.ObjectAnimator
@@ -24,40 +24,56 @@
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.R
 import com.android.systemui.animation.Interpolators
+import com.android.systemui.media.controls.ui.SquigglyProgress
 
 /**
  * Observer for changes from SeekBarViewModel.
  *
  * <p>Updates the seek bar views in response to changes to the model.
  */
-open class SeekBarObserver(
-    private val holder: MediaViewHolder
-) : Observer<SeekBarViewModel.Progress> {
+open class SeekBarObserver(private val holder: MediaViewHolder) :
+    Observer<SeekBarViewModel.Progress> {
 
     companion object {
         @JvmStatic val RESET_ANIMATION_DURATION_MS: Int = 750
         @JvmStatic val RESET_ANIMATION_THRESHOLD_MS: Int = 250
     }
 
-    val seekBarEnabledMaxHeight = holder.seekBar.context.resources
-        .getDimensionPixelSize(R.dimen.qs_media_enabled_seekbar_height)
-    val seekBarDisabledHeight = holder.seekBar.context.resources
-        .getDimensionPixelSize(R.dimen.qs_media_disabled_seekbar_height)
-    val seekBarEnabledVerticalPadding = holder.seekBar.context.resources
-                .getDimensionPixelSize(R.dimen.qs_media_session_enabled_seekbar_vertical_padding)
-    val seekBarDisabledVerticalPadding = holder.seekBar.context.resources
-                .getDimensionPixelSize(R.dimen.qs_media_session_disabled_seekbar_vertical_padding)
+    val seekBarEnabledMaxHeight =
+        holder.seekBar.context.resources.getDimensionPixelSize(
+            R.dimen.qs_media_enabled_seekbar_height
+        )
+    val seekBarDisabledHeight =
+        holder.seekBar.context.resources.getDimensionPixelSize(
+            R.dimen.qs_media_disabled_seekbar_height
+        )
+    val seekBarEnabledVerticalPadding =
+        holder.seekBar.context.resources.getDimensionPixelSize(
+            R.dimen.qs_media_session_enabled_seekbar_vertical_padding
+        )
+    val seekBarDisabledVerticalPadding =
+        holder.seekBar.context.resources.getDimensionPixelSize(
+            R.dimen.qs_media_session_disabled_seekbar_vertical_padding
+        )
     var seekBarResetAnimator: Animator? = null
 
     init {
-        val seekBarProgressWavelength = holder.seekBar.context.resources
-                .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_wavelength).toFloat()
-        val seekBarProgressAmplitude = holder.seekBar.context.resources
-                .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_amplitude).toFloat()
-        val seekBarProgressPhase = holder.seekBar.context.resources
-                .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_phase).toFloat()
-        val seekBarProgressStrokeWidth = holder.seekBar.context.resources
-                .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_stroke_width).toFloat()
+        val seekBarProgressWavelength =
+            holder.seekBar.context.resources
+                .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_wavelength)
+                .toFloat()
+        val seekBarProgressAmplitude =
+            holder.seekBar.context.resources
+                .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_amplitude)
+                .toFloat()
+        val seekBarProgressPhase =
+            holder.seekBar.context.resources
+                .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_phase)
+                .toFloat()
+        val seekBarProgressStrokeWidth =
+            holder.seekBar.context.resources
+                .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_stroke_width)
+                .toFloat()
         val progressDrawable = holder.seekBar.progressDrawable as? SquigglyProgress
         progressDrawable?.let {
             it.waveLength = seekBarProgressWavelength
@@ -97,16 +113,18 @@
         }
 
         holder.seekBar.setMax(data.duration)
-        val totalTimeString = DateUtils.formatElapsedTime(
-            data.duration / DateUtils.SECOND_IN_MILLIS)
+        val totalTimeString =
+            DateUtils.formatElapsedTime(data.duration / DateUtils.SECOND_IN_MILLIS)
         if (data.scrubbing) {
             holder.scrubbingTotalTimeView.text = totalTimeString
         }
 
         data.elapsedTime?.let {
             if (!data.scrubbing && !(seekBarResetAnimator?.isRunning ?: false)) {
-                if (it <= RESET_ANIMATION_THRESHOLD_MS &&
-                        holder.seekBar.progress > RESET_ANIMATION_THRESHOLD_MS) {
+                if (
+                    it <= RESET_ANIMATION_THRESHOLD_MS &&
+                        holder.seekBar.progress > RESET_ANIMATION_THRESHOLD_MS
+                ) {
                     // This animation resets for every additional update to zero.
                     val animator = buildResetAnimator(it)
                     animator.start()
@@ -116,24 +134,29 @@
                 }
             }
 
-            val elapsedTimeString = DateUtils.formatElapsedTime(
-                it / DateUtils.SECOND_IN_MILLIS)
+            val elapsedTimeString = DateUtils.formatElapsedTime(it / DateUtils.SECOND_IN_MILLIS)
             if (data.scrubbing) {
                 holder.scrubbingElapsedTimeView.text = elapsedTimeString
             }
 
-            holder.seekBar.contentDescription = holder.seekBar.context.getString(
-                R.string.controls_media_seekbar_description,
-                elapsedTimeString,
-                totalTimeString
-            )
+            holder.seekBar.contentDescription =
+                holder.seekBar.context.getString(
+                    R.string.controls_media_seekbar_description,
+                    elapsedTimeString,
+                    totalTimeString
+                )
         }
     }
 
     @VisibleForTesting
     open fun buildResetAnimator(targetTime: Int): Animator {
-        val animator = ObjectAnimator.ofInt(holder.seekBar, "progress",
-                holder.seekBar.progress, targetTime + RESET_ANIMATION_DURATION_MS)
+        val animator =
+            ObjectAnimator.ofInt(
+                holder.seekBar,
+                "progress",
+                holder.seekBar.progress,
+                targetTime + RESET_ANIMATION_DURATION_MS
+            )
         animator.setAutoCancel(true)
         animator.duration = RESET_ANIMATION_DURATION_MS.toLong()
         animator.interpolator = Interpolators.EMPHASIZED
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
similarity index 74%
rename from packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
index 17ebfec..bba5f35 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.models.player
 
 import android.media.MediaMetadata
 import android.media.session.MediaController
@@ -33,7 +33,6 @@
 import com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
 import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.util.concurrency.RepeatableExecutor
 import javax.inject.Inject
@@ -43,8 +42,8 @@
 
 private fun PlaybackState.isInMotion(): Boolean {
     return this.state == PlaybackState.STATE_PLAYING ||
-            this.state == PlaybackState.STATE_FAST_FORWARDING ||
-            this.state == PlaybackState.STATE_REWINDING
+        this.state == PlaybackState.STATE_FAST_FORWARDING ||
+        this.state == PlaybackState.STATE_REWINDING
 }
 
 /**
@@ -60,8 +59,8 @@
         val updateTime = this.getLastPositionUpdateTime()
         val currentTime = SystemClock.elapsedRealtime()
         if (updateTime > 0) {
-            var position = (this.playbackSpeed * (currentTime - updateTime)).toLong() +
-                    this.getPosition()
+            var position =
+                (this.playbackSpeed * (currentTime - updateTime)).toLong() + this.getPosition()
             if (duration >= 0 && position > duration) {
                 position = duration.toLong()
             } else if (position < 0) {
@@ -74,7 +73,9 @@
 }
 
 /** ViewModel for seek bar in QS media player. */
-class SeekBarViewModel @Inject constructor(
+class SeekBarViewModel
+@Inject
+constructor(
     @Background private val bgExecutor: RepeatableExecutor,
     private val falsingManager: FalsingManager,
 ) {
@@ -87,9 +88,7 @@
             }
             _progress.postValue(value)
         }
-    private val _progress = MutableLiveData<Progress>().apply {
-        postValue(_data)
-    }
+    private val _progress = MutableLiveData<Progress>().apply { postValue(_data) }
     val progress: LiveData<Progress>
         get() = _progress
     private var controller: MediaController? = null
@@ -101,20 +100,21 @@
             }
         }
     private var playbackState: PlaybackState? = null
-    private var callback = object : MediaController.Callback() {
-        override fun onPlaybackStateChanged(state: PlaybackState?) {
-            playbackState = state
-            if (playbackState == null || PlaybackState.STATE_NONE.equals(playbackState)) {
+    private var callback =
+        object : MediaController.Callback() {
+            override fun onPlaybackStateChanged(state: PlaybackState?) {
+                playbackState = state
+                if (playbackState == null || PlaybackState.STATE_NONE.equals(playbackState)) {
+                    clearController()
+                } else {
+                    checkIfPollingNeeded()
+                }
+            }
+
+            override fun onSessionDestroyed() {
                 clearController()
-            } else {
-                checkIfPollingNeeded()
             }
         }
-
-        override fun onSessionDestroyed() {
-            clearController()
-        }
-    }
     private var cancel: Runnable? = null
 
     /** Indicates if the seek interaction is considered a false guesture. */
@@ -122,12 +122,13 @@
 
     /** Listening state (QS open or closed) is used to control polling of progress. */
     var listening = true
-        set(value) = bgExecutor.execute {
-            if (field != value) {
-                field = value
-                checkIfPollingNeeded()
+        set(value) =
+            bgExecutor.execute {
+                if (field != value) {
+                    field = value
+                    checkIfPollingNeeded()
+                }
             }
-        }
 
     private var scrubbingChangeListener: ScrubbingChangeListener? = null
     private var enabledChangeListener: EnabledChangeListener? = null
@@ -145,14 +146,13 @@
 
     lateinit var logSeek: () -> Unit
 
-    /**
-     * Event indicating that the user has started interacting with the seek bar.
-     */
+    /** Event indicating that the user has started interacting with the seek bar. */
     @AnyThread
-    fun onSeekStarting() = bgExecutor.execute {
-        scrubbing = true
-        isFalseSeek = false
-    }
+    fun onSeekStarting() =
+        bgExecutor.execute {
+            scrubbing = true
+            isFalseSeek = false
+        }
 
     /**
      * Event indicating that the user has moved the seek bar.
@@ -160,47 +160,51 @@
      * @param position Current location in the track.
      */
     @AnyThread
-    fun onSeekProgress(position: Long) = bgExecutor.execute {
-        if (scrubbing) {
-            // The user hasn't yet finished their touch gesture, so only update the data for visual
-            // feedback and don't update [controller] yet.
-            _data = _data.copy(elapsedTime = position.toInt())
-        } else {
-            // The seek progress came from an a11y action and we should immediately update to the
-            // new position. (a11y actions to change the seekbar position don't trigger
-            // SeekBar.OnSeekBarChangeListener.onStartTrackingTouch or onStopTrackingTouch.)
-            onSeek(position)
+    fun onSeekProgress(position: Long) =
+        bgExecutor.execute {
+            if (scrubbing) {
+                // The user hasn't yet finished their touch gesture, so only update the data for
+                // visual
+                // feedback and don't update [controller] yet.
+                _data = _data.copy(elapsedTime = position.toInt())
+            } else {
+                // The seek progress came from an a11y action and we should immediately update to
+                // the
+                // new position. (a11y actions to change the seekbar position don't trigger
+                // SeekBar.OnSeekBarChangeListener.onStartTrackingTouch or onStopTrackingTouch.)
+                onSeek(position)
+            }
         }
-    }
 
-    /**
-     * Event indicating that the seek interaction is a false gesture and it should be ignored.
-     */
+    /** Event indicating that the seek interaction is a false gesture and it should be ignored. */
     @AnyThread
-    fun onSeekFalse() = bgExecutor.execute {
-        if (scrubbing) {
-            isFalseSeek = true
+    fun onSeekFalse() =
+        bgExecutor.execute {
+            if (scrubbing) {
+                isFalseSeek = true
+            }
         }
-    }
 
     /**
      * Handle request to change the current position in the media track.
      * @param position Place to seek to in the track.
      */
     @AnyThread
-    fun onSeek(position: Long) = bgExecutor.execute {
-        if (isFalseSeek) {
-            scrubbing = false
-            checkPlaybackPosition()
-        } else {
-            logSeek()
-            controller?.transportControls?.seekTo(position)
-            // Invalidate the cached playbackState to avoid the thumb jumping back to the previous
-            // position.
-            playbackState = null
-            scrubbing = false
+    fun onSeek(position: Long) =
+        bgExecutor.execute {
+            if (isFalseSeek) {
+                scrubbing = false
+                checkPlaybackPosition()
+            } else {
+                logSeek()
+                controller?.transportControls?.seekTo(position)
+                // Invalidate the cached playbackState to avoid the thumb jumping back to the
+                // previous
+                // position.
+                playbackState = null
+                scrubbing = false
+            }
         }
-    }
 
     /**
      * Updates media information.
@@ -217,11 +221,18 @@
         val seekAvailable = ((playbackState?.actions ?: 0L) and PlaybackState.ACTION_SEEK_TO) != 0L
         val position = playbackState?.position?.toInt()
         val duration = mediaMetadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt() ?: 0
-        val playing = NotificationMediaManager
-                .isPlayingState(playbackState?.state ?: PlaybackState.STATE_NONE)
-        val enabled = if (playbackState == null ||
-                playbackState?.getState() == PlaybackState.STATE_NONE ||
-                (duration <= 0)) false else true
+        val playing =
+            NotificationMediaManager.isPlayingState(
+                playbackState?.state ?: PlaybackState.STATE_NONE
+            )
+        val enabled =
+            if (
+                playbackState == null ||
+                    playbackState?.getState() == PlaybackState.STATE_NONE ||
+                    (duration <= 0)
+            )
+                false
+            else true
         _data = Progress(enabled, seekAvailable, playing, scrubbing, position, duration)
         checkIfPollingNeeded()
     }
@@ -232,26 +243,26 @@
      * This should be called when the media session behind the controller has been destroyed.
      */
     @AnyThread
-    fun clearController() = bgExecutor.execute {
-        controller = null
-        playbackState = null
-        cancel?.run()
-        cancel = null
-        _data = _data.copy(enabled = false)
-    }
+    fun clearController() =
+        bgExecutor.execute {
+            controller = null
+            playbackState = null
+            cancel?.run()
+            cancel = null
+            _data = _data.copy(enabled = false)
+        }
 
-    /**
-     * Call to clean up any resources.
-     */
+    /** Call to clean up any resources. */
     @AnyThread
-    fun onDestroy() = bgExecutor.execute {
-        controller = null
-        playbackState = null
-        cancel?.run()
-        cancel = null
-        scrubbingChangeListener = null
-        enabledChangeListener = null
-    }
+    fun onDestroy() =
+        bgExecutor.execute {
+            controller = null
+            playbackState = null
+            cancel?.run()
+            cancel = null
+            scrubbingChangeListener = null
+            enabledChangeListener = null
+        }
 
     @WorkerThread
     private fun checkPlaybackPosition() {
@@ -267,8 +278,12 @@
         val needed = listening && !scrubbing && playbackState?.isInMotion() ?: false
         if (needed) {
             if (cancel == null) {
-                cancel = bgExecutor.executeRepeatedly(this::checkPlaybackPosition, 0L,
-                        POSITION_UPDATE_INTERVAL_MILLIS)
+                cancel =
+                    bgExecutor.executeRepeatedly(
+                        this::checkPlaybackPosition,
+                        0L,
+                        POSITION_UPDATE_INTERVAL_MILLIS
+                    )
             }
         } else {
             cancel?.run()
@@ -333,11 +348,7 @@
         }
 
         override fun onStopTrackingTouch(bar: SeekBar) {
-            // in addition to the normal functionality of both functions.
-            // isFalseTouch returns true if there is a real/false tap since it is not a move.
-            // isFalseTap returns true if there is a real/false move since it is not a tap.
-            if (falsingManager.isFalseTouch(MEDIA_SEEKBAR) &&
-                    falsingManager.isFalseTap(LOW_PENALTY)) {
+            if (falsingManager.isFalseTouch(MEDIA_SEEKBAR)) {
                 viewModel.onSeekFalse()
             }
             viewModel.onSeek(bar.progress.toLong())
@@ -358,9 +369,10 @@
         // Gesture detector helps decide which touch events to intercept.
         private val detector = GestureDetectorCompat(bar.context, this)
         // Velocity threshold used to decide when a fling is considered a false gesture.
-        private val flingVelocity: Int = ViewConfiguration.get(bar.context).run {
-            getScaledMinimumFlingVelocity() * MIN_FLING_VELOCITY_SCALE_FACTOR
-        }
+        private val flingVelocity: Int =
+            ViewConfiguration.get(bar.context).run {
+                getScaledMinimumFlingVelocity() * MIN_FLING_VELOCITY_SCALE_FACTOR
+            }
         // Indicates if the gesture should go to the seek bar or if it should be intercepted.
         private var shouldGoToSeekBar = false
 
@@ -390,9 +402,9 @@
         /**
          * Handle down events that press down on the thumb.
          *
-         * On the down action, determine a target box around the thumb to know when a scroll
-         * gesture starts by clicking on the thumb. The target box will be used by subsequent
-         * onScroll events.
+         * On the down action, determine a target box around the thumb to know when a scroll gesture
+         * starts by clicking on the thumb. The target box will be used by subsequent onScroll
+         * events.
          *
          * Returns true when the down event hits within the target box of the thumb.
          */
@@ -403,17 +415,19 @@
             // TODO: account for thumb offset
             val progress = bar.getProgress()
             val range = bar.max - bar.min
-            val widthFraction = if (range > 0) {
-                (progress - bar.min).toDouble() / range
-            } else {
-                0.0
-            }
+            val widthFraction =
+                if (range > 0) {
+                    (progress - bar.min).toDouble() / range
+                } else {
+                    0.0
+                }
             val availableWidth = bar.width - padL - padR
-            val thumbX = if (bar.isLayoutRtl()) {
-                padL + availableWidth * (1 - widthFraction)
-            } else {
-                padL + availableWidth * widthFraction
-            }
+            val thumbX =
+                if (bar.isLayoutRtl()) {
+                    padL + availableWidth * (1 - widthFraction)
+                } else {
+                    padL + availableWidth * widthFraction
+                }
             // Set the min, max boundaries of the thumb box.
             // I'm cheating by using the height of the seek bar as the width of the box.
             val halfHeight: Int = bar.height / 2
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
new file mode 100644
index 0000000..1a10b18
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2020 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.media.controls.models.recommendation
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import com.android.systemui.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"
+
+/** ViewHolder for a Smartspace media recommendation. */
+class RecommendationViewHolder private constructor(itemView: View) {
+
+    val recommendations = itemView as TransitionLayout
+
+    // Recommendation screen
+    val cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
+    val mediaCoverItems =
+        listOf<ImageView>(
+            itemView.requireViewById(R.id.media_cover1),
+            itemView.requireViewById(R.id.media_cover2),
+            itemView.requireViewById(R.id.media_cover3)
+        )
+    val mediaCoverContainers =
+        listOf<ViewGroup>(
+            itemView.requireViewById(R.id.media_cover1_container),
+            itemView.requireViewById(R.id.media_cover2_container),
+            itemView.requireViewById(R.id.media_cover3_container)
+        )
+    val mediaTitles: List<TextView> =
+        listOf(
+            itemView.requireViewById(R.id.media_title1),
+            itemView.requireViewById(R.id.media_title2),
+            itemView.requireViewById(R.id.media_title3)
+        )
+    val mediaSubtitles: List<TextView> =
+        listOf(
+            itemView.requireViewById(R.id.media_subtitle1),
+            itemView.requireViewById(R.id.media_subtitle2),
+            itemView.requireViewById(R.id.media_subtitle3)
+        )
+
+    val gutsViewHolder = GutsViewHolder(itemView)
+
+    init {
+        (recommendations.background as IlluminationDrawable).let { background ->
+            mediaCoverContainers.forEach { background.registerLightSource(it) }
+            background.registerLightSource(gutsViewHolder.cancel)
+            background.registerLightSource(gutsViewHolder.dismiss)
+            background.registerLightSource(gutsViewHolder.settings)
+        }
+    }
+
+    fun marquee(start: Boolean, delay: Long) {
+        gutsViewHolder.marquee(start, delay, TAG)
+    }
+
+    companion object {
+        /**
+         * Creates a RecommendationViewHolder.
+         *
+         * @param inflater LayoutInflater to use to inflate the layout.
+         * @param parent Parent of inflated view.
+         */
+        @JvmStatic
+        fun create(inflater: LayoutInflater, parent: ViewGroup): RecommendationViewHolder {
+            val itemView =
+                inflater.inflate(
+                    R.layout.media_smartspace_recommendations,
+                    parent,
+                    false /* attachToRoot */
+                )
+            // Because this media view (a TransitionLayout) is used to measure and layout the views
+            // in various states before being attached to its parent, we can't depend on the default
+            // LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction.
+            itemView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
+            return RecommendationViewHolder(itemView)
+        }
+
+        // Res Ids for the control components on the recommendation view.
+        val controlsIds =
+            setOf(
+                R.id.recommendation_card_icon,
+                R.id.media_cover1,
+                R.id.media_cover2,
+                R.id.media_cover3,
+                R.id.media_cover1_container,
+                R.id.media_cover2_container,
+                R.id.media_cover3_container,
+                R.id.media_title1,
+                R.id.media_title2,
+                R.id.media_title3,
+                R.id.media_subtitle1,
+                R.id.media_subtitle2,
+                R.id.media_subtitle3
+            )
+
+        val mediaTitlesAndSubtitlesIds =
+            setOf(
+                R.id.media_title1,
+                R.id.media_title2,
+                R.id.media_title3,
+                R.id.media_subtitle1,
+                R.id.media_subtitle2,
+                R.id.media_subtitle3
+            )
+
+        val mediaContainersIds =
+            setOf(
+                R.id.media_cover1_container,
+                R.id.media_cover2_container,
+                R.id.media_cover3_container
+            )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt
similarity index 75%
rename from packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt
index c8f17d9..1df42c6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt
@@ -14,7 +14,7 @@
  * limitations under the License
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.models.recommendation
 
 import android.app.smartspace.SmartspaceAction
 import android.content.Context
@@ -22,55 +22,41 @@
 import android.content.pm.PackageManager
 import android.text.TextUtils
 import android.util.Log
+import androidx.annotation.VisibleForTesting
 import com.android.internal.logging.InstanceId
-import com.android.systemui.media.MediaControlPanel.KEY_SMARTSPACE_APP_NAME
+
+@VisibleForTesting const val KEY_SMARTSPACE_APP_NAME = "KEY_SMARTSPACE_APP_NAME"
 
 /** State of a Smartspace media recommendations view. */
 data class SmartspaceMediaData(
-    /**
-     * Unique id of a Smartspace media target.
-     */
+    /** Unique id of a Smartspace media target. */
     val targetId: String,
-    /**
-     * Indicates if the status is active.
-     */
+    /** Indicates if the status is active. */
     val isActive: Boolean,
-    /**
-     * Package name of the media recommendations' provider-app.
-     */
+    /** Package name of the media recommendations' provider-app. */
     val packageName: String,
-    /**
-     * Action to perform when the card is tapped. Also contains the target's extra info.
-     */
+    /** Action to perform when the card is tapped. Also contains the target's extra info. */
     val cardAction: SmartspaceAction?,
-    /**
-     * List of media recommendations.
-     */
+    /** List of media recommendations. */
     val recommendations: List<SmartspaceAction>,
-    /**
-     * Intent for the user's initiated dismissal.
-     */
+    /** Intent for the user's initiated dismissal. */
     val dismissIntent: Intent?,
-    /**
-     * The timestamp in milliseconds that headphone is connected.
-     */
+    /** The timestamp in milliseconds that headphone is connected. */
     val headphoneConnectionTimeMillis: Long,
-    /**
-     * Instance ID for [MediaUiEventLogger]
-     */
+    /** Instance ID for [MediaUiEventLogger] */
     val instanceId: InstanceId
 ) {
     /**
      * Indicates if all the data is valid.
      *
      * TODO(b/230333302): Make MediaControlPanel more flexible so that we can display fewer than
+     * ```
      *     [NUM_REQUIRED_RECOMMENDATIONS].
+     * ```
      */
     fun isValid() = getValidRecommendations().size >= NUM_REQUIRED_RECOMMENDATIONS
 
-    /**
-     * Returns the list of [recommendations] that have valid data.
-     */
+    /** Returns the list of [recommendations] that have valid data. */
     fun getValidRecommendations() = recommendations.filter { it.icon != null }
 
     /** Returns the upstream app name if available. */
@@ -89,9 +75,10 @@
         Log.w(
             TAG,
             "Package $packageName does not have a main launcher activity. " +
-                    "Fallback to full app name")
+                "Fallback to full app name"
+        )
         return try {
-            val applicationInfo = packageManager.getApplicationInfo(packageName,  /* flags= */ 0)
+            val applicationInfo = packageManager.getApplicationInfo(packageName, /* flags= */ 0)
             packageManager.getApplicationLabel(applicationInfo)
         } catch (e: PackageManager.NameNotFoundException) {
             null
diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt
similarity index 71%
rename from packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt
index 140a1fe..a7ed69a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt
@@ -1,4 +1,20 @@
-package com.android.systemui.media
+/*
+ * 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.media.controls.models.recommendation
 
 import android.app.smartspace.SmartspaceTarget
 import android.util.Log
@@ -23,7 +39,7 @@
         smartspaceMediaTargetListeners.remove(smartspaceTargetListener)
     }
 
-    /** Updates Smartspace data and propagates it to any listeners.  */
+    /** Updates Smartspace data and propagates it to any listeners. */
     override fun onTargetsAvailable(targets: List<SmartspaceTarget>) {
         // Filter out non-media targets.
         val mediaTargets = mutableListOf<SmartspaceTarget>()
diff --git a/packages/SystemUI/src/com/android/systemui/media/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
similarity index 88%
rename from packages/SystemUI/src/com/android/systemui/media/LocalMediaManagerFactory.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
index 94a0835..ff763d8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/LocalMediaManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
@@ -14,20 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.pipeline
 
 import android.content.Context
-
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.settingslib.media.InfoMediaManager
 import com.android.settingslib.media.LocalMediaManager
-
 import javax.inject.Inject
 
-/**
- * Factory to create [LocalMediaManager] objects.
- */
-class LocalMediaManagerFactory @Inject constructor(
+/** Factory to create [LocalMediaManager] objects. */
+class LocalMediaManagerFactory
+@Inject
+constructor(
     private val context: Context,
     private val localBluetoothManager: LocalBluetoothManager?
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatest.kt
similarity index 78%
rename from packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatest.kt
index 311973a..789ef40 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatest.kt
@@ -14,15 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.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 javax.inject.Inject
 
-/**
- * Combines [MediaDataManager.Listener] events with [MediaDeviceManager.Listener] events.
- */
-class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener,
-        MediaDeviceManager.Listener {
+/** Combines [MediaDataManager.Listener] events with [MediaDeviceManager.Listener] events. */
+class MediaDataCombineLatest @Inject constructor() :
+    MediaDataManager.Listener, MediaDeviceManager.Listener {
 
     private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
     private val entries: MutableMap<String, Pair<MediaData?, MediaDeviceData?>> = mutableMapOf()
@@ -60,11 +61,7 @@
         listeners.toSet().forEach { it.onSmartspaceMediaDataRemoved(key, immediately) }
     }
 
-    override fun onMediaDeviceChanged(
-        key: String,
-        oldKey: String?,
-        data: MediaDeviceData?
-    ) {
+    override fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?) {
         if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
             entries[key] = entries.remove(oldKey)?.first to data
             update(key, oldKey)
@@ -83,9 +80,7 @@
      */
     fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener)
 
-    /**
-     * Remove a listener registered with addListener.
-     */
+    /** Remove a listener registered with addListener. */
     fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)
 
     private fun update(key: String, oldKey: String?) {
@@ -93,18 +88,14 @@
         if (entry != null && device != null) {
             val data = entry.copy(device = device)
             val listenersCopy = listeners.toSet()
-            listenersCopy.forEach {
-                it.onMediaDataLoaded(key, oldKey, data)
-            }
+            listenersCopy.forEach { it.onMediaDataLoaded(key, oldKey, data) }
         }
     }
 
     private fun remove(key: String) {
         entries.remove(key)?.let {
             val listenersCopy = listeners.toSet()
-            listenersCopy.forEach {
-                it.onMediaDataRemoved(key)
-            }
+            listenersCopy.forEach { it.onMediaDataRemoved(key) }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
similarity index 71%
rename from packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index e0c8d66..45b319b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.pipeline
 
 import android.content.Context
 import android.os.SystemProperties
@@ -23,6 +23,9 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 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.SmartspaceMediaData
+import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.settings.CurrentUserTracker
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.util.time.SystemClock
@@ -34,7 +37,8 @@
 
 private const val TAG = "MediaDataFilter"
 private const val DEBUG = true
-private const val EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME = ("com.google" +
+private const val EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME =
+    ("com.google" +
         ".android.apps.gsa.staticplugins.opa.smartspace.ExportedSmartspaceTrampolineActivity")
 private const val RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY = "resumable_media_max_age_seconds"
 
@@ -43,8 +47,8 @@
  * available within this time window, smartspace recommendations will be shown instead.
  */
 @VisibleForTesting
-internal val SMARTSPACE_MAX_AGE = SystemProperties
-        .getLong("debug.sysui.smartspace_max_age", TimeUnit.MINUTES.toMillis(30))
+internal val SMARTSPACE_MAX_AGE =
+    SystemProperties.getLong("debug.sysui.smartspace_max_age", TimeUnit.MINUTES.toMillis(30))
 
 /**
  * Filters data updates from [MediaDataCombineLatest] based on the current user ID, and handles user
@@ -54,7 +58,9 @@
  * This is added at the end of the pipeline since we may still need to handle callbacks from
  * background users (e.g. timeouts).
  */
-class MediaDataFilter @Inject constructor(
+class MediaDataFilter
+@Inject
+constructor(
     private val context: Context,
     private val broadcastDispatcher: BroadcastDispatcher,
     private val broadcastSender: BroadcastSender,
@@ -76,12 +82,13 @@
     private var reactivatedKey: String? = null
 
     init {
-        userTracker = object : CurrentUserTracker(broadcastDispatcher) {
-            override fun onUserSwitched(newUserId: Int) {
-                // Post this so we can be sure lockscreenUserManager already got the broadcast
-                executor.execute { handleUserSwitched(newUserId) }
+        userTracker =
+            object : CurrentUserTracker(broadcastDispatcher) {
+                override fun onUserSwitched(newUserId: Int) {
+                    // Post this so we can be sure lockscreenUserManager already got the broadcast
+                    executor.execute { handleUserSwitched(newUserId) }
+                }
             }
-        }
         userTracker.startTracking()
     }
 
@@ -108,9 +115,7 @@
         userEntries.put(key, data)
 
         // Notify listeners
-        listeners.forEach {
-            it.onMediaDataLoaded(key, oldKey, data)
-        }
+        listeners.forEach { it.onMediaDataLoaded(key, oldKey, data) }
     }
 
     override fun onSmartspaceMediaDataLoaded(
@@ -128,14 +133,11 @@
         smartspaceMediaData = data
 
         // Before forwarding the smartspace target, first check if we have recently inactive media
-        val sorted = userEntries.toSortedMap(compareBy {
-            userEntries.get(it)?.lastActive ?: -1
-        })
+        val sorted = userEntries.toSortedMap(compareBy { userEntries.get(it)?.lastActive ?: -1 })
         val timeSinceActive = timeSinceActiveForMostRecentMedia(sorted)
         var smartspaceMaxAgeMillis = SMARTSPACE_MAX_AGE
         data.cardAction?.let {
-            val smartspaceMaxAgeSeconds =
-                it.extras.getLong(RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY, 0)
+            val smartspaceMaxAgeSeconds = it.extras.getLong(RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY, 0)
             if (smartspaceMaxAgeSeconds > 0) {
                 smartspaceMaxAgeMillis = TimeUnit.SECONDS.toMillis(smartspaceMaxAgeSeconds)
             }
@@ -152,13 +154,21 @@
                 Log.d(TAG, "reactivating $lastActiveKey instead of smartspace")
                 reactivatedKey = lastActiveKey
                 val mediaData = sorted.get(lastActiveKey)!!.copy(active = true)
-                logger.logRecommendationActivated(mediaData.appUid, mediaData.packageName,
-                    mediaData.instanceId)
+                logger.logRecommendationActivated(
+                    mediaData.appUid,
+                    mediaData.packageName,
+                    mediaData.instanceId
+                )
                 listeners.forEach {
-                    it.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData,
-                            receivedSmartspaceCardLatency =
+                    it.onMediaDataLoaded(
+                        lastActiveKey,
+                        lastActiveKey,
+                        mediaData,
+                        receivedSmartspaceCardLatency =
                             (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis)
-                                    .toInt(), isSsReactivated = true)
+                                .toInt(),
+                        isSsReactivated = true
+                    )
                 }
             }
         } else {
@@ -170,8 +180,10 @@
             Log.d(TAG, "Invalid recommendation data. Skip showing the rec card")
             return
         }
-        logger.logRecommendationAdded(smartspaceMediaData.packageName,
-            smartspaceMediaData.instanceId)
+        logger.logRecommendationAdded(
+            smartspaceMediaData.packageName,
+            smartspaceMediaData.instanceId
+        )
         listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
     }
 
@@ -179,9 +191,7 @@
         allEntries.remove(key)
         userEntries.remove(key)?.let {
             // Only notify listeners if something actually changed
-            listeners.forEach {
-                it.onMediaDataRemoved(key)
-            }
+            listeners.forEach { it.onMediaDataRemoved(key) }
         }
     }
 
@@ -194,16 +204,17 @@
             // Notify listeners to update with actual active value
             userEntries.get(lastActiveKey)?.let { mediaData ->
                 listeners.forEach {
-                    it.onMediaDataLoaded(
-                            lastActiveKey, lastActiveKey, mediaData, immediately)
+                    it.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData, immediately)
                 }
             }
         }
 
         if (smartspaceMediaData.isActive) {
-            smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
-                targetId = smartspaceMediaData.targetId,
-                instanceId = smartspaceMediaData.instanceId)
+            smartspaceMediaData =
+                EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+                    targetId = smartspaceMediaData.targetId,
+                    instanceId = smartspaceMediaData.instanceId
+                )
         }
         listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) }
     }
@@ -218,25 +229,19 @@
         userEntries.clear()
         keyCopy.forEach {
             if (DEBUG) Log.d(TAG, "Removing $it after user change")
-            listenersCopy.forEach { listener ->
-                listener.onMediaDataRemoved(it)
-            }
+            listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) }
         }
 
         allEntries.forEach { (key, data) ->
             if (lockscreenUserManager.isCurrentProfile(data.userId)) {
                 if (DEBUG) Log.d(TAG, "Re-adding $key after user change")
                 userEntries.put(key, data)
-                listenersCopy.forEach { listener ->
-                    listener.onMediaDataLoaded(key, null, data)
-                }
+                listenersCopy.forEach { listener -> listener.onMediaDataLoaded(key, null, data) }
             }
         }
     }
 
-    /**
-     * Invoked when the user has dismissed the media carousel
-     */
+    /** Invoked when the user has dismissed the media carousel */
     fun onSwipeToDismiss() {
         if (DEBUG) Log.d(TAG, "Media carousel swiped away")
         val mediaKeys = userEntries.keys.toSet()
@@ -247,55 +252,52 @@
         if (smartspaceMediaData.isActive) {
             val dismissIntent = smartspaceMediaData.dismissIntent
             if (dismissIntent == null) {
-                Log.w(TAG, "Cannot create dismiss action click action: " +
-                        "extras missing dismiss_intent.")
-            } else if (dismissIntent.getComponent() != null &&
-                    dismissIntent.getComponent().getClassName()
-                    == EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME) {
+                Log.w(
+                    TAG,
+                    "Cannot create dismiss action click action: " + "extras missing dismiss_intent."
+                )
+            } else if (
+                dismissIntent.getComponent() != null &&
+                    dismissIntent.getComponent().getClassName() ==
+                        EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME
+            ) {
                 // Dismiss the card Smartspace data through Smartspace trampoline activity.
                 context.startActivity(dismissIntent)
             } else {
                 broadcastSender.sendBroadcast(dismissIntent)
             }
-            smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
-                targetId = smartspaceMediaData.targetId,
-                instanceId = smartspaceMediaData.instanceId)
-            mediaDataManager.dismissSmartspaceRecommendation(smartspaceMediaData.targetId,
-                delay = 0L)
+            smartspaceMediaData =
+                EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+                    targetId = smartspaceMediaData.targetId,
+                    instanceId = smartspaceMediaData.instanceId
+                )
+            mediaDataManager.dismissSmartspaceRecommendation(
+                smartspaceMediaData.targetId,
+                delay = 0L
+            )
         }
     }
 
-    /**
-     * Are there any active media entries, including the recommendation?
-     */
-    fun hasActiveMediaOrRecommendation() = userEntries.any { it.value.active } ||
+    /** Are there any active media entries, including the recommendation? */
+    fun hasActiveMediaOrRecommendation() =
+        userEntries.any { it.value.active } ||
             (smartspaceMediaData.isActive &&
                 (smartspaceMediaData.isValid() || reactivatedKey != null))
 
-    /**
-     * Are there any media entries we should display?
-     */
-    fun hasAnyMediaOrRecommendation() = userEntries.isNotEmpty() ||
-            (smartspaceMediaData.isActive && smartspaceMediaData.isValid())
+    /** Are there any media entries we should display? */
+    fun hasAnyMediaOrRecommendation() =
+        userEntries.isNotEmpty() || (smartspaceMediaData.isActive && smartspaceMediaData.isValid())
 
-    /**
-     * Are there any media notifications active (excluding the recommendation)?
-     */
+    /** Are there any media notifications active (excluding the recommendation)? */
     fun hasActiveMedia() = userEntries.any { it.value.active }
 
-    /**
-     * Are there any media entries we should display (excluding the recommendation)?
-     */
+    /** Are there any media entries we should display (excluding the recommendation)? */
     fun hasAnyMedia() = userEntries.isNotEmpty()
 
-    /**
-     * Add a listener for filtered [MediaData] changes
-     */
+    /** Add a listener for filtered [MediaData] changes */
     fun addListener(listener: MediaDataManager.Listener) = _listeners.add(listener)
 
-    /**
-     * Remove a listener that was registered with addListener
-     */
+    /** Remove a listener that was registered with addListener */
     fun removeListener(listener: MediaDataManager.Listener) = _listeners.remove(listener)
 
     /**
@@ -315,8 +317,6 @@
 
         val now = systemClock.elapsedRealtime()
         val lastActiveKey = sortedEntries.lastKey() // most recently active
-        return sortedEntries.get(lastActiveKey)?.let {
-            now - it.lastActive
-        } ?: Long.MAX_VALUE
+        return sortedEntries.get(lastActiveKey)?.let { now - it.lastActive } ?: Long.MAX_VALUE
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
similarity index 68%
rename from packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 5096a797..14dd990 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.pipeline
 
 import android.app.Notification
 import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
@@ -57,6 +57,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.SmartspaceMediaData
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider
+import com.android.systemui.media.controls.resume.MediaResumeListener
+import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaFlags
+import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
@@ -75,17 +86,19 @@
 import javax.inject.Inject
 
 // URI fields to try loading album art from
-private val ART_URIS = arrayOf(
+private val ART_URIS =
+    arrayOf(
         MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
         MediaMetadata.METADATA_KEY_ART_URI,
         MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
-)
+    )
 
 private const val TAG = "MediaDataManager"
 private const val DEBUG = true
 private const val EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY = "dismiss_intent"
 
-private val LOADING = MediaData(
+private val LOADING =
+    MediaData(
         userId = -1,
         initialized = false,
         app = null,
@@ -102,37 +115,41 @@
         active = true,
         resumeAction = null,
         instanceId = InstanceId.fakeInstanceId(-1),
-        appUid = Process.INVALID_UID)
+        appUid = Process.INVALID_UID
+    )
 
 @VisibleForTesting
-internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData(
-    targetId = "INVALID",
-    isActive = false,
-    packageName = "INVALID",
-    cardAction = null,
-    recommendations = emptyList(),
-    dismissIntent = null,
-    headphoneConnectionTimeMillis = 0,
-    instanceId = InstanceId.fakeInstanceId(-1))
+internal val EMPTY_SMARTSPACE_MEDIA_DATA =
+    SmartspaceMediaData(
+        targetId = "INVALID",
+        isActive = false,
+        packageName = "INVALID",
+        cardAction = null,
+        recommendations = emptyList(),
+        dismissIntent = null,
+        headphoneConnectionTimeMillis = 0,
+        instanceId = InstanceId.fakeInstanceId(-1)
+    )
 
 fun isMediaNotification(sbn: StatusBarNotification): Boolean {
     return sbn.notification.isMediaNotification()
 }
 
 /**
- * Allow recommendations from smartspace to show in media controls.
- * Requires [Utils.useQsMediaPlayer] to be enabled.
- * On by default, but can be disabled by setting to 0
+ * Allow recommendations from smartspace to show in media controls. Requires
+ * [Utils.useQsMediaPlayer] to be enabled. On by default, but can be disabled by setting to 0
  */
 private fun allowMediaRecommendations(context: Context): Boolean {
-    val flag = Settings.Secure.getInt(context.contentResolver,
-            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 1)
+    val flag =
+        Settings.Secure.getInt(
+            context.contentResolver,
+            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
+            1
+        )
     return Utils.useQsMediaPlayer(context) && flag > 0
 }
 
-/**
- * A class that facilitates management and loading of Media Data, ready for binding.
- */
+/** A class that facilitates management and loading of Media Data, ready for binding. */
 @SysUISingleton
 class MediaDataManager(
     private val context: Context,
@@ -159,24 +176,24 @@
 
     companion object {
         // UI surface label for subscribing Smartspace updates.
-        @JvmField
-        val SMARTSPACE_UI_SURFACE_LABEL = "media_data_manager"
+        @JvmField val SMARTSPACE_UI_SURFACE_LABEL = "media_data_manager"
 
         // Smartspace package name's extra key.
-        @JvmField
-        val EXTRAS_MEDIA_SOURCE_PACKAGE_NAME = "package_name"
+        @JvmField val EXTRAS_MEDIA_SOURCE_PACKAGE_NAME = "package_name"
 
         // Maximum number of actions allowed in compact view
-        @JvmField
-        val MAX_COMPACT_ACTIONS = 3
+        @JvmField val MAX_COMPACT_ACTIONS = 3
 
         // Maximum number of actions allowed in expanded view
-        @JvmField
-        val MAX_NOTIFICATION_ACTIONS = MediaViewHolder.genericButtonIds.size
+        @JvmField val MAX_NOTIFICATION_ACTIONS = MediaViewHolder.genericButtonIds.size
     }
 
-    private val themeText = com.android.settingslib.Utils.getColorAttr(context,
-            com.android.internal.R.attr.textColorPrimary).defaultColor
+    private val themeText =
+        com.android.settingslib.Utils.getColorAttr(
+                context,
+                com.android.internal.R.attr.textColorPrimary
+            )
+            .defaultColor
 
     // Internal listeners are part of the internal pipeline. External listeners (those registered
     // with [MediaDeviceManager.addListener]) receive events after they have propagated through
@@ -192,9 +209,7 @@
     private var smartspaceSession: SmartspaceSession? = null
     private var allowMediaRecommendations = allowMediaRecommendations(context)
 
-    /**
-     * Check whether this notification is an RCN
-     */
+    /** Check whether this notification is an RCN */
     private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean {
         return sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE)
     }
@@ -219,29 +234,44 @@
         tunerService: TunerService,
         mediaFlags: MediaFlags,
         logger: MediaUiEventLogger
-    ) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory,
-            broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
-            mediaSessionBasedFilter, mediaDeviceManager, mediaDataCombineLatest, mediaDataFilter,
-            activityStarter, smartspaceMediaDataProvider, Utils.useMediaResumption(context),
-            Utils.useQsMediaPlayer(context), clock, tunerService, mediaFlags, logger)
+    ) : this(
+        context,
+        backgroundExecutor,
+        foregroundExecutor,
+        mediaControllerFactory,
+        broadcastDispatcher,
+        dumpManager,
+        mediaTimeoutListener,
+        mediaResumeListener,
+        mediaSessionBasedFilter,
+        mediaDeviceManager,
+        mediaDataCombineLatest,
+        mediaDataFilter,
+        activityStarter,
+        smartspaceMediaDataProvider,
+        Utils.useMediaResumption(context),
+        Utils.useQsMediaPlayer(context),
+        clock,
+        tunerService,
+        mediaFlags,
+        logger
+    )
 
-    private val appChangeReceiver = object : BroadcastReceiver() {
-        override fun onReceive(context: Context, intent: Intent) {
-            when (intent.action) {
-                Intent.ACTION_PACKAGES_SUSPENDED -> {
-                    val packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST)
-                    packages?.forEach {
-                        removeAllForPackage(it)
+    private val appChangeReceiver =
+        object : BroadcastReceiver() {
+            override fun onReceive(context: Context, intent: Intent) {
+                when (intent.action) {
+                    Intent.ACTION_PACKAGES_SUSPENDED -> {
+                        val packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+                        packages?.forEach { removeAllForPackage(it) }
                     }
-                }
-                Intent.ACTION_PACKAGE_REMOVED, Intent.ACTION_PACKAGE_RESTARTED -> {
-                    intent.data?.encodedSchemeSpecificPart?.let {
-                        removeAllForPackage(it)
+                    Intent.ACTION_PACKAGE_REMOVED,
+                    Intent.ACTION_PACKAGE_RESTARTED -> {
+                        intent.data?.encodedSchemeSpecificPart?.let { removeAllForPackage(it) }
                     }
                 }
             }
         }
-    }
 
     init {
         dumpManager.registerDumpable(TAG, this)
@@ -262,20 +292,23 @@
 
         // Set up links back into the pipeline for listeners that need to send events upstream.
         mediaTimeoutListener.timeoutCallback = { key: String, timedOut: Boolean ->
-            setTimedOut(key, timedOut) }
+            setTimedOut(key, timedOut)
+        }
         mediaTimeoutListener.stateCallback = { key: String, state: PlaybackState ->
-            updateState(key, state) }
+            updateState(key, state)
+        }
         mediaResumeListener.setManager(this)
         mediaDataFilter.mediaDataManager = this
 
         val suspendFilter = IntentFilter(Intent.ACTION_PACKAGES_SUSPENDED)
         broadcastDispatcher.registerReceiver(appChangeReceiver, suspendFilter, null, UserHandle.ALL)
 
-        val uninstallFilter = IntentFilter().apply {
-            addAction(Intent.ACTION_PACKAGE_REMOVED)
-            addAction(Intent.ACTION_PACKAGE_RESTARTED)
-            addDataScheme("package")
-        }
+        val uninstallFilter =
+            IntentFilter().apply {
+                addAction(Intent.ACTION_PACKAGE_REMOVED)
+                addAction(Intent.ACTION_PACKAGE_RESTARTED)
+                addDataScheme("package")
+            }
         // BroadcastDispatcher does not allow filters with data schemes
         context.registerReceiver(appChangeReceiver, uninstallFilter)
 
@@ -283,8 +316,10 @@
         smartspaceMediaDataProvider.registerListener(this)
         val smartspaceManager: SmartspaceManager =
             context.getSystemService(SmartspaceManager::class.java)
-        smartspaceSession = smartspaceManager.createSmartspaceSession(
-            SmartspaceConfig.Builder(context, SMARTSPACE_UI_SURFACE_LABEL).build())
+        smartspaceSession =
+            smartspaceManager.createSmartspaceSession(
+                SmartspaceConfig.Builder(context, SMARTSPACE_UI_SURFACE_LABEL).build()
+            )
         smartspaceSession?.let {
             it.addOnTargetsAvailableListener(
                 // Use a new thread listening to Smartspace updates instead of using the existing
@@ -296,17 +331,24 @@
                 Executors.newCachedThreadPool(),
                 SmartspaceSession.OnTargetsAvailableListener { targets ->
                     smartspaceMediaDataProvider.onTargetsAvailable(targets)
-                })
+                }
+            )
         }
         smartspaceSession?.let { it.requestSmartspaceUpdate() }
-        tunerService.addTunable(object : TunerService.Tunable {
-            override fun onTuningChanged(key: String?, newValue: String?) {
-                allowMediaRecommendations = allowMediaRecommendations(context)
-                if (!allowMediaRecommendations) {
-                    dismissSmartspaceRecommendation(key = smartspaceMediaData.targetId, delay = 0L)
+        tunerService.addTunable(
+            object : TunerService.Tunable {
+                override fun onTuningChanged(key: String?, newValue: String?) {
+                    allowMediaRecommendations = allowMediaRecommendations(context)
+                    if (!allowMediaRecommendations) {
+                        dismissSmartspaceRecommendation(
+                            key = smartspaceMediaData.targetId,
+                            delay = 0L
+                        )
+                    }
                 }
-            }
-        }, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION)
+            },
+            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION
+        )
     }
 
     fun destroy() {
@@ -321,10 +363,7 @@
             val oldKey = findExistingEntry(key, sbn.packageName)
             if (oldKey == null) {
                 val instanceId = logger.getNewInstanceId()
-                val temp = LOADING.copy(
-                    packageName = sbn.packageName,
-                    instanceId = instanceId
-                )
+                val temp = LOADING.copy(packageName = sbn.packageName, instanceId = instanceId)
                 mediaEntries.put(key, temp)
                 logEvent = true
             } else if (oldKey != key) {
@@ -342,9 +381,7 @@
     private fun removeAllForPackage(packageName: String) {
         Assert.isMainThread()
         val toRemove = mediaEntries.filter { it.value.packageName == packageName }
-        toRemove.forEach {
-            removeEntry(it.key)
-        }
+        toRemove.forEach { removeEntry(it.key) }
     }
 
     fun setResumeAction(key: String, action: Runnable?) {
@@ -366,32 +403,41 @@
         // Resume controls don't have a notification key, so store by package name instead
         if (!mediaEntries.containsKey(packageName)) {
             val instanceId = logger.getNewInstanceId()
-            val appUid = try {
-                context.packageManager.getApplicationInfo(packageName, 0)?.uid!!
-            } catch (e: PackageManager.NameNotFoundException) {
-                Log.w(TAG, "Could not get app UID for $packageName", e)
-                Process.INVALID_UID
-            }
+            val appUid =
+                try {
+                    context.packageManager.getApplicationInfo(packageName, 0)?.uid!!
+                } catch (e: PackageManager.NameNotFoundException) {
+                    Log.w(TAG, "Could not get app UID for $packageName", e)
+                    Process.INVALID_UID
+                }
 
-            val resumeData = LOADING.copy(
-                packageName = packageName,
-                resumeAction = action,
-                hasCheckedForResume = true,
-                instanceId = instanceId,
-                appUid = appUid
-            )
+            val resumeData =
+                LOADING.copy(
+                    packageName = packageName,
+                    resumeAction = action,
+                    hasCheckedForResume = true,
+                    instanceId = instanceId,
+                    appUid = appUid
+                )
             mediaEntries.put(packageName, resumeData)
             logger.logResumeMediaAdded(appUid, packageName, instanceId)
         }
         backgroundExecutor.execute {
-            loadMediaDataInBgForResumption(userId, desc, action, token, appName, appIntent,
-                packageName)
+            loadMediaDataInBgForResumption(
+                userId,
+                desc,
+                action,
+                token,
+                appName,
+                appIntent,
+                packageName
+            )
         }
     }
 
     /**
-     * Check if there is an existing entry that matches the key or package name.
-     * Returns the key that matches, or null if not found.
+     * Check if there is an existing entry that matches the key or package name. Returns the key
+     * that matches, or null if not found.
      */
     private fun findExistingEntry(key: String, packageName: String): String? {
         if (mediaEntries.containsKey(key)) {
@@ -410,32 +456,24 @@
         oldKey: String?,
         logEvent: Boolean = false
     ) {
-        backgroundExecutor.execute {
-            loadMediaDataInBg(key, sbn, oldKey, logEvent)
-        }
+        backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, logEvent) }
     }
 
-    /**
-     * Add a listener for changes in this class
-     */
+    /** Add a listener for changes in this class */
     fun addListener(listener: Listener) {
         // mediaDataFilter is the current end of the internal pipeline. Register external
         // listeners as listeners to it.
         mediaDataFilter.addListener(listener)
     }
 
-    /**
-     * Remove a listener for changes in this class
-     */
+    /** Remove a listener for changes in this class */
     fun removeListener(listener: Listener) {
         // Since mediaDataFilter is the current end of the internal pipelie, external listeners
         // have been registered to it. So, they need to be removed from it too.
         mediaDataFilter.removeListener(listener)
     }
 
-    /**
-     * Add a listener for internal events.
-     */
+    /** Add a listener for internal events. */
     private fun addInternalListener(listener: Listener) = internalListeners.add(listener)
 
     /**
@@ -483,8 +521,8 @@
     }
 
     /**
-     * Called whenever the player has been paused or stopped for a while, or swiped from QQS.
-     * This will make the player not active anymore, hiding it from QQS and Keyguard.
+     * Called whenever the player has been paused or stopped for a while, or swiped from QQS. This
+     * will make the player not active anymore, hiding it from QQS and Keyguard.
      * @see MediaData.active
      */
     internal fun setTimedOut(key: String, timedOut: Boolean, forceUpdate: Boolean = false) {
@@ -506,9 +544,7 @@
         }
     }
 
-    /**
-     * Called when the player's [PlaybackState] has been updated with new actions and/or state
-     */
+    /** Called when the player's [PlaybackState] has been updated with new actions and/or state */
     private fun updateState(key: String, state: PlaybackState) {
         mediaEntries.get(key)?.let {
             val token = it.token
@@ -516,22 +552,23 @@
                 if (DEBUG) Log.d(TAG, "State updated, but token was null")
                 return
             }
-            val actions = createActionsFromState(it.packageName,
-                    mediaControllerFactory.create(it.token), UserHandle(it.userId))
+            val actions =
+                createActionsFromState(
+                    it.packageName,
+                    mediaControllerFactory.create(it.token),
+                    UserHandle(it.userId)
+                )
 
             // Control buttons
             // If flag is enabled and controller has a PlaybackState,
             // create actions from session info
             // otherwise, no need to update semantic actions.
-            val data = if (actions != null) {
-                it.copy(
-                        semanticActions = actions,
-                        isPlaying = isPlayingState(state.state))
-            } else {
-                it.copy(
-                        isPlaying = isPlayingState(state.state)
-                )
-            }
+            val data =
+                if (actions != null) {
+                    it.copy(semanticActions = actions, isPlaying = isPlayingState(state.state))
+                } else {
+                    it.copy(isPlaying = isPlayingState(state.state))
+                }
             if (DEBUG) Log.d(TAG, "State updated outside of notification")
             onMediaDataLoaded(key, key, data)
         }
@@ -544,9 +581,7 @@
         notifyMediaDataRemoved(key)
     }
 
-    /**
-     * Dismiss a media entry. Returns false if the key was not found.
-     */
+    /** Dismiss a media entry. Returns false if the key was not found. */
     fun dismissMediaData(key: String, delay: Long): Boolean {
         val existed = mediaEntries[key] != null
         backgroundExecutor.execute {
@@ -564,9 +599,8 @@
     }
 
     /**
-     * Called whenever the recommendation has been expired, or swiped from QQS.
-     * This will make the recommendation view to not be shown anymore during this headphone
-     * connection session.
+     * Called whenever the recommendation has been expired, or swiped from QQS. This will make the
+     * recommendation view to not be shown anymore during this headphone connection session.
      */
     fun dismissSmartspaceRecommendation(key: String, delay: Long) {
         if (smartspaceMediaData.targetId != key || !smartspaceMediaData.isValid()) {
@@ -576,13 +610,16 @@
 
         if (DEBUG) Log.d(TAG, "Dismissing Smartspace media target")
         if (smartspaceMediaData.isActive) {
-            smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
-                targetId = smartspaceMediaData.targetId,
-                instanceId = smartspaceMediaData.instanceId)
+            smartspaceMediaData =
+                EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+                    targetId = smartspaceMediaData.targetId,
+                    instanceId = smartspaceMediaData.instanceId
+                )
         }
         foregroundExecutor.executeDelayed(
-            { notifySmartspaceMediaDataRemoved(
-                smartspaceMediaData.targetId, immediately = true) }, delay)
+            { notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = true) },
+            delay
+        )
     }
 
     private fun loadMediaDataInBgForResumption(
@@ -610,11 +647,12 @@
         if (artworkBitmap == null && desc.iconUri != null) {
             artworkBitmap = loadBitmapFromUri(desc.iconUri!!)
         }
-        val artworkIcon = if (artworkBitmap != null) {
-            Icon.createWithBitmap(artworkBitmap)
-        } else {
-            null
-        }
+        val artworkIcon =
+            if (artworkBitmap != null) {
+                Icon.createWithBitmap(artworkBitmap)
+            } else {
+                null
+            }
 
         val currentEntry = mediaEntries.get(packageName)
         val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
@@ -623,13 +661,34 @@
         val mediaAction = getResumeMediaAction(resumeAction)
         val lastActive = systemClock.elapsedRealtime()
         foregroundExecutor.execute {
-            onMediaDataLoaded(packageName, null, MediaData(userId, true, appName,
-                    null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0),
-                    MediaButton(playOrPause = mediaAction), packageName, token, appIntent,
-                    device = null, active = false,
-                    resumeAction = resumeAction, resumption = true, notificationKey = packageName,
-                    hasCheckedForResume = true, lastActive = lastActive, instanceId = instanceId,
-                    appUid = appUid))
+            onMediaDataLoaded(
+                packageName,
+                null,
+                MediaData(
+                    userId,
+                    true,
+                    appName,
+                    null,
+                    desc.subtitle,
+                    desc.title,
+                    artworkIcon,
+                    listOf(mediaAction),
+                    listOf(0),
+                    MediaButton(playOrPause = mediaAction),
+                    packageName,
+                    token,
+                    appIntent,
+                    device = null,
+                    active = false,
+                    resumeAction = resumeAction,
+                    resumption = true,
+                    notificationKey = packageName,
+                    hasCheckedForResume = true,
+                    lastActive = lastActive,
+                    instanceId = instanceId,
+                    appUid = appUid
+                )
+            )
         }
     }
 
@@ -639,8 +698,11 @@
         oldKey: String?,
         logEvent: Boolean = false
     ) {
-        val token = sbn.notification.extras.getParcelable(
-                Notification.EXTRA_MEDIA_SESSION, MediaSession.Token::class.java)
+        val token =
+            sbn.notification.extras.getParcelable(
+                Notification.EXTRA_MEDIA_SESSION,
+                MediaSession.Token::class.java
+            )
         if (token == null) {
             return
         }
@@ -648,10 +710,12 @@
         val metadata = mediaController.metadata
         val notif: Notification = sbn.notification
 
-        val appInfo = notif.extras.getParcelable(
-            Notification.EXTRA_BUILDER_APPLICATION_INFO,
-            ApplicationInfo::class.java
-        ) ?: getAppInfoFromPackage(sbn.packageName)
+        val appInfo =
+            notif.extras.getParcelable(
+                Notification.EXTRA_BUILDER_APPLICATION_INFO,
+                ApplicationInfo::class.java
+            )
+                ?: getAppInfoFromPackage(sbn.packageName)
 
         // Album art
         var artworkBitmap = metadata?.let { loadBitmapFromUri(it) }
@@ -661,11 +725,12 @@
         if (artworkBitmap == null) {
             artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
         }
-        val artWorkIcon = if (artworkBitmap == null) {
-            notif.getLargeIcon()
-        } else {
-            Icon.createWithBitmap(artworkBitmap)
-        }
+        val artWorkIcon =
+            if (artworkBitmap == null) {
+                notif.getLargeIcon()
+            } else {
+                Icon.createWithBitmap(artworkBitmap)
+            }
 
         // App name
         val appName = getAppName(sbn, appInfo)
@@ -694,17 +759,27 @@
             val extras = sbn.notification.extras
             val deviceName = extras.getCharSequence(Notification.EXTRA_MEDIA_REMOTE_DEVICE, null)
             val deviceIcon = extras.getInt(Notification.EXTRA_MEDIA_REMOTE_ICON, -1)
-            val deviceIntent = extras.getParcelable(
-                    Notification.EXTRA_MEDIA_REMOTE_INTENT, PendingIntent::class.java)
+            val deviceIntent =
+                extras.getParcelable(
+                    Notification.EXTRA_MEDIA_REMOTE_INTENT,
+                    PendingIntent::class.java
+                )
             Log.d(TAG, "$key is RCN for $deviceName")
 
             if (deviceName != null && deviceIcon > -1) {
                 // Name and icon must be present, but intent may be null
                 val enabled = deviceIntent != null && deviceIntent.isActivity
-                val deviceDrawable = Icon.createWithResource(sbn.packageName, deviceIcon)
+                val deviceDrawable =
+                    Icon.createWithResource(sbn.packageName, deviceIcon)
                         .loadDrawable(sbn.getPackageContext(context))
-                device = MediaDeviceData(enabled, deviceDrawable, deviceName, deviceIntent,
-                        showBroadcastButton = false)
+                device =
+                    MediaDeviceData(
+                        enabled,
+                        deviceDrawable,
+                        deviceName,
+                        deviceIntent,
+                        showBroadcastButton = false
+                    )
             }
         }
 
@@ -721,10 +796,13 @@
         }
 
         val playbackLocation =
-                if (isRemoteCastNotification(sbn)) MediaData.PLAYBACK_CAST_REMOTE
-                else if (mediaController.playbackInfo?.playbackType ==
-                        MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) MediaData.PLAYBACK_LOCAL
-                else MediaData.PLAYBACK_CAST_LOCAL
+            if (isRemoteCastNotification(sbn)) MediaData.PLAYBACK_CAST_REMOTE
+            else if (
+                mediaController.playbackInfo?.playbackType ==
+                    MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL
+            )
+                MediaData.PLAYBACK_LOCAL
+            else MediaData.PLAYBACK_CAST_LOCAL
         val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) } ?: null
 
         val currentEntry = mediaEntries.get(key)
@@ -742,13 +820,36 @@
             val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
             val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
             val active = mediaEntries[key]?.active ?: true
-            onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, appName,
-                    smallIcon, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed,
-                    semanticActions, sbn.packageName, token, notif.contentIntent, device,
-                    active, resumeAction = resumeAction, playbackLocation = playbackLocation,
-                    notificationKey = key, hasCheckedForResume = hasCheckedForResume,
-                    isPlaying = isPlaying, isClearable = sbn.isClearable(),
-                    lastActive = lastActive, instanceId = instanceId, appUid = appUid))
+            onMediaDataLoaded(
+                key,
+                oldKey,
+                MediaData(
+                    sbn.normalizedUserId,
+                    true,
+                    appName,
+                    smallIcon,
+                    artist,
+                    song,
+                    artWorkIcon,
+                    actionIcons,
+                    actionsToShowCollapsed,
+                    semanticActions,
+                    sbn.packageName,
+                    token,
+                    notif.contentIntent,
+                    device,
+                    active,
+                    resumeAction = resumeAction,
+                    playbackLocation = playbackLocation,
+                    notificationKey = key,
+                    hasCheckedForResume = hasCheckedForResume,
+                    isPlaying = isPlaying,
+                    isClearable = sbn.isClearable(),
+                    lastActive = lastActive,
+                    instanceId = instanceId,
+                    appUid = appUid
+                )
+            )
         }
     }
 
@@ -774,27 +875,33 @@
         }
     }
 
-    /**
-     * Generate action buttons based on notification actions
-     */
-    private fun createActionsFromNotification(sbn: StatusBarNotification):
-            Pair<List<MediaAction>, List<Int>> {
+    /** Generate action buttons based on notification actions */
+    private fun createActionsFromNotification(
+        sbn: StatusBarNotification
+    ): Pair<List<MediaAction>, List<Int>> {
         val notif = sbn.notification
         val actionIcons: MutableList<MediaAction> = ArrayList()
         val actions = notif.actions
-        var actionsToShowCollapsed = notif.extras.getIntArray(
-            Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList() ?: mutableListOf()
+        var actionsToShowCollapsed =
+            notif.extras.getIntArray(Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList()
+                ?: mutableListOf()
         if (actionsToShowCollapsed.size > MAX_COMPACT_ACTIONS) {
-            Log.e(TAG, "Too many compact actions for ${sbn.key}," +
-                "limiting to first $MAX_COMPACT_ACTIONS")
+            Log.e(
+                TAG,
+                "Too many compact actions for ${sbn.key}," +
+                    "limiting to first $MAX_COMPACT_ACTIONS"
+            )
             actionsToShowCollapsed = actionsToShowCollapsed.subList(0, MAX_COMPACT_ACTIONS)
         }
 
         if (actions != null) {
             for ((index, action) in actions.withIndex()) {
                 if (index == MAX_NOTIFICATION_ACTIONS) {
-                    Log.w(TAG, "Too many notification actions for ${sbn.key}," +
-                        " limiting to first $MAX_NOTIFICATION_ACTIONS")
+                    Log.w(
+                        TAG,
+                        "Too many notification actions for ${sbn.key}," +
+                            " limiting to first $MAX_NOTIFICATION_ACTIONS"
+                    )
                     break
                 }
                 if (action.getIcon() == null) {
@@ -802,33 +909,38 @@
                     actionsToShowCollapsed.remove(index)
                     continue
                 }
-                val runnable = if (action.actionIntent != null) {
-                    Runnable {
-                        if (action.actionIntent.isActivity) {
-                            activityStarter.startPendingIntentDismissingKeyguard(
-                                action.actionIntent)
-                        } else if (action.isAuthenticationRequired()) {
-                            activityStarter.dismissKeyguardThenExecute({
-                                var result = sendPendingIntent(action.actionIntent)
-                                result
-                            }, {}, true)
-                        } else {
-                            sendPendingIntent(action.actionIntent)
+                val runnable =
+                    if (action.actionIntent != null) {
+                        Runnable {
+                            if (action.actionIntent.isActivity) {
+                                activityStarter.startPendingIntentDismissingKeyguard(
+                                    action.actionIntent
+                                )
+                            } else if (action.isAuthenticationRequired()) {
+                                activityStarter.dismissKeyguardThenExecute(
+                                    {
+                                        var result = sendPendingIntent(action.actionIntent)
+                                        result
+                                    },
+                                    {},
+                                    true
+                                )
+                            } else {
+                                sendPendingIntent(action.actionIntent)
+                            }
                         }
+                    } else {
+                        null
                     }
-                } else {
-                    null
-                }
-                val mediaActionIcon = if (action.getIcon()?.getType() == Icon.TYPE_RESOURCE) {
-                    Icon.createWithResource(sbn.packageName, action.getIcon()!!.getResId())
-                } else {
-                    action.getIcon()
-                }.setTint(themeText).loadDrawable(context)
-                val mediaAction = MediaAction(
-                    mediaActionIcon,
-                    runnable,
-                    action.title,
-                    null)
+                val mediaActionIcon =
+                    if (action.getIcon()?.getType() == Icon.TYPE_RESOURCE) {
+                            Icon.createWithResource(sbn.packageName, action.getIcon()!!.getResId())
+                        } else {
+                            action.getIcon()
+                        }
+                        .setTint(themeText)
+                        .loadDrawable(context)
+                val mediaAction = MediaAction(mediaActionIcon, runnable, action.title, null)
                 actionIcons.add(mediaAction)
             }
         }
@@ -841,7 +953,9 @@
      * @param packageName Package name for the media app
      * @param controller MediaController for the current session
      * @return a Pair consisting of a list of media actions, and a list of ints representing which
+     * ```
      *      of those actions should be shown in the compact player
+     * ```
      */
     private fun createActionsFromState(
         packageName: String,
@@ -854,59 +968,69 @@
         }
 
         // First, check for standard actions
-        val playOrPause = if (isConnectingState(state.state)) {
-            // Spinner needs to be animating to render anything. Start it here.
-            val drawable = context.getDrawable(
-                com.android.internal.R.drawable.progress_small_material)
-            (drawable as Animatable).start()
-            MediaAction(
-                drawable,
-                null, // no action to perform when clicked
-                context.getString(R.string.controls_media_button_connecting),
-                context.getDrawable(R.drawable.ic_media_connecting_container),
-                // Specify a rebind id to prevent the spinner from restarting on later binds.
-                com.android.internal.R.drawable.progress_small_material
-            )
-        } else if (isPlayingState(state.state)) {
-            getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE)
-        } else {
-            getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY)
-        }
-        val prevButton = getStandardAction(controller, state.actions,
-            PlaybackState.ACTION_SKIP_TO_PREVIOUS)
-        val nextButton = getStandardAction(controller, state.actions,
-            PlaybackState.ACTION_SKIP_TO_NEXT)
+        val playOrPause =
+            if (isConnectingState(state.state)) {
+                // Spinner needs to be animating to render anything. Start it here.
+                val drawable =
+                    context.getDrawable(com.android.internal.R.drawable.progress_small_material)
+                (drawable as Animatable).start()
+                MediaAction(
+                    drawable,
+                    null, // no action to perform when clicked
+                    context.getString(R.string.controls_media_button_connecting),
+                    context.getDrawable(R.drawable.ic_media_connecting_container),
+                    // Specify a rebind id to prevent the spinner from restarting on later binds.
+                    com.android.internal.R.drawable.progress_small_material
+                )
+            } else if (isPlayingState(state.state)) {
+                getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE)
+            } else {
+                getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY)
+            }
+        val prevButton =
+            getStandardAction(controller, state.actions, PlaybackState.ACTION_SKIP_TO_PREVIOUS)
+        val nextButton =
+            getStandardAction(controller, state.actions, PlaybackState.ACTION_SKIP_TO_NEXT)
 
         // Then, create a way to build any custom actions that will be needed
-        val customActions = state.customActions.asSequence().filterNotNull().map {
-            getCustomAction(state, packageName, controller, it)
-        }.iterator()
+        val customActions =
+            state.customActions
+                .asSequence()
+                .filterNotNull()
+                .map { getCustomAction(state, packageName, controller, it) }
+                .iterator()
         fun nextCustomAction() = if (customActions.hasNext()) customActions.next() else null
 
         // Finally, assign the remaining button slots: play/pause A B C D
         // A = previous, else custom action (if not reserved)
         // B = next, else custom action (if not reserved)
         // C and D are always custom actions
-        val reservePrev = controller.extras?.getBoolean(
-            MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV) == true
-        val reserveNext = controller.extras?.getBoolean(
-            MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT) == true
+        val reservePrev =
+            controller.extras?.getBoolean(
+                MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
+            ) == true
+        val reserveNext =
+            controller.extras?.getBoolean(
+                MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
+            ) == true
 
-        val prevOrCustom = if (prevButton != null) {
-            prevButton
-        } else if (!reservePrev) {
-            nextCustomAction()
-        } else {
-            null
-        }
+        val prevOrCustom =
+            if (prevButton != null) {
+                prevButton
+            } else if (!reservePrev) {
+                nextCustomAction()
+            } else {
+                null
+            }
 
-        val nextOrCustom = if (nextButton != null) {
-            nextButton
-        } else if (!reserveNext) {
-            nextCustomAction()
-        } else {
-            null
-        }
+        val nextOrCustom =
+            if (nextButton != null) {
+                nextButton
+            } else if (!reserveNext) {
+                nextCustomAction()
+            } else {
+                null
+            }
 
         return MediaButton(
             playOrPause,
@@ -925,11 +1049,14 @@
      * @param controller MediaController for the session
      * @param stateActions The actions included with the session's [PlaybackState]
      * @param action A [PlaybackState.Actions] value representing what action to generate. One of:
+     * ```
      *      [PlaybackState.ACTION_PLAY]
      *      [PlaybackState.ACTION_PAUSE]
      *      [PlaybackState.ACTION_SKIP_TO_PREVIOUS]
      *      [PlaybackState.ACTION_SKIP_TO_NEXT]
-     * @return A [MediaAction] with correct values set, or null if the state doesn't support it
+     * @return
+     * ```
+     * A [MediaAction] with correct values set, or null if the state doesn't support it
      */
     private fun getStandardAction(
         controller: MediaController,
@@ -977,20 +1104,18 @@
         }
     }
 
-    /**
-     * Check whether the actions from a [PlaybackState] include a specific action
-     */
+    /** Check whether the actions from a [PlaybackState] include a specific action */
     private fun includesAction(stateActions: Long, @PlaybackState.Actions action: Long): Boolean {
-        if ((action == PlaybackState.ACTION_PLAY || action == PlaybackState.ACTION_PAUSE) &&
-                (stateActions and PlaybackState.ACTION_PLAY_PAUSE > 0L)) {
+        if (
+            (action == PlaybackState.ACTION_PLAY || action == PlaybackState.ACTION_PAUSE) &&
+                (stateActions and PlaybackState.ACTION_PLAY_PAUSE > 0L)
+        ) {
             return true
         }
         return (stateActions and action != 0L)
     }
 
-    /**
-     * Get a [MediaAction] representing a [PlaybackState.CustomAction]
-     */
+    /** Get a [MediaAction] representing a [PlaybackState.CustomAction] */
     private fun getCustomAction(
         state: PlaybackState,
         packageName: String,
@@ -1005,9 +1130,7 @@
         )
     }
 
-    /**
-     * Load a bitmap from the various Art metadata URIs
-     */
+    /** Load a bitmap from the various Art metadata URIs */
     private fun loadBitmapFromUri(metadata: MediaMetadata): Bitmap? {
         for (uri in ART_URIS) {
             val uriString = metadata.getString(uri)
@@ -1042,16 +1165,18 @@
             return null
         }
 
-        if (!uri.scheme.equals(ContentResolver.SCHEME_CONTENT) &&
+        if (
+            !uri.scheme.equals(ContentResolver.SCHEME_CONTENT) &&
                 !uri.scheme.equals(ContentResolver.SCHEME_ANDROID_RESOURCE) &&
-                !uri.scheme.equals(ContentResolver.SCHEME_FILE)) {
+                !uri.scheme.equals(ContentResolver.SCHEME_FILE)
+        ) {
             return null
         }
 
         val source = ImageDecoder.createSource(context.getContentResolver(), uri)
         return try {
-            ImageDecoder.decodeBitmap(source) {
-                decoder, info, source -> decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+            ImageDecoder.decodeBitmap(source) { decoder, _, _ ->
+                decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
             }
         } catch (e: IOException) {
             Log.e(TAG, "Unable to load bitmap", e)
@@ -1065,25 +1190,23 @@
     private fun getResumeMediaAction(action: Runnable): MediaAction {
         return MediaAction(
             Icon.createWithResource(context, R.drawable.ic_media_play)
-                .setTint(themeText).loadDrawable(context),
+                .setTint(themeText)
+                .loadDrawable(context),
             action,
             context.getString(R.string.controls_media_resume),
             context.getDrawable(R.drawable.ic_media_play_container)
         )
     }
 
-    fun onMediaDataLoaded(
-        key: String,
-        oldKey: String?,
-        data: MediaData
-    ) = traceSection("MediaDataManager#onMediaDataLoaded") {
-        Assert.isMainThread()
-        if (mediaEntries.containsKey(key)) {
-            // Otherwise this was removed already
-            mediaEntries.put(key, data)
-            notifyMediaDataLoaded(key, oldKey, data)
+    fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) =
+        traceSection("MediaDataManager#onMediaDataLoaded") {
+            Assert.isMainThread()
+            if (mediaEntries.containsKey(key)) {
+                // Otherwise this was removed already
+                mediaEntries.put(key, data)
+                notifyMediaDataLoaded(key, oldKey, data)
+            }
         }
-    }
 
     override fun onSmartspaceTargetsUpdated(targets: List<Parcelable>) {
         if (!allowMediaRecommendations) {
@@ -1100,9 +1223,11 @@
                 if (DEBUG) {
                     Log.d(TAG, "Set Smartspace media to be inactive for the data update")
                 }
-                smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
-                    targetId = smartspaceMediaData.targetId,
-                    instanceId = smartspaceMediaData.instanceId)
+                smartspaceMediaData =
+                    EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+                        targetId = smartspaceMediaData.targetId,
+                        instanceId = smartspaceMediaData.instanceId
+                    )
                 notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = false)
             }
             1 -> {
@@ -1113,15 +1238,16 @@
                 }
                 if (DEBUG) Log.d(TAG, "Forwarding Smartspace media update.")
                 smartspaceMediaData = toSmartspaceMediaData(newMediaTarget, isActive = true)
-                notifySmartspaceMediaDataLoaded(
-                    smartspaceMediaData.targetId, smartspaceMediaData)
+                notifySmartspaceMediaDataLoaded(smartspaceMediaData.targetId, smartspaceMediaData)
             }
             else -> {
                 // There should NOT be more than 1 Smartspace media update. When it happens, it
                 // indicates a bad state or an error. Reset the status accordingly.
                 Log.wtf(TAG, "More than 1 Smartspace Media Update. Resetting the status...")
                 notifySmartspaceMediaDataRemoved(
-                    smartspaceMediaData.targetId, false /* immediately */)
+                    smartspaceMediaData.targetId,
+                    false /* immediately */
+                )
                 smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
             }
         }
@@ -1130,14 +1256,21 @@
     fun onNotificationRemoved(key: String) {
         Assert.isMainThread()
         val removed = mediaEntries.remove(key)
-        if (useMediaResumption && removed?.resumeAction != null && removed?.isLocalSession()) {
+        if (useMediaResumption && removed?.resumeAction != null && removed.isLocalSession()) {
             Log.d(TAG, "Not removing $key because resumable")
             // Move to resume key (aka package name) if that key doesn't already exist.
             val resumeAction = getResumeMediaAction(removed.resumeAction!!)
-            val updated = removed.copy(token = null, actions = listOf(resumeAction),
+            val updated =
+                removed.copy(
+                    token = null,
+                    actions = listOf(resumeAction),
                     semanticActions = MediaButton(playOrPause = resumeAction),
-                    actionsToShowInCompact = listOf(0), active = false, resumption = true,
-                    isPlaying = false, isClearable = true)
+                    actionsToShowInCompact = listOf(0),
+                    active = false,
+                    resumption = true,
+                    isPlaying = false,
+                    isClearable = true
+                )
             val pkg = removed.packageName
             val migrate = mediaEntries.put(pkg, updated) == null
             // Notify listeners of "new" controls when migrating or removed and update when not
@@ -1179,33 +1312,27 @@
         }
     }
 
-    /**
-     * Invoked when the user has dismissed the media carousel
-     */
+    /** Invoked when the user has dismissed the media carousel */
     fun onSwipeToDismiss() = mediaDataFilter.onSwipeToDismiss()
 
-    /**
-     * Are there any media notifications active, including the recommendations?
-     */
+    /** Are there any media notifications active, including the recommendations? */
     fun hasActiveMediaOrRecommendation() = mediaDataFilter.hasActiveMediaOrRecommendation()
 
     /**
      * Are there any media entries we should display, including the recommendations?
-     * If resumption is enabled, this will include inactive players
-     * If resumption is disabled, we only want to show active players
+     * - If resumption is enabled, this will include inactive players
+     * - If resumption is disabled, we only want to show active players
      */
     fun hasAnyMediaOrRecommendation() = mediaDataFilter.hasAnyMediaOrRecommendation()
 
-    /**
-     * Are there any resume media notifications active, excluding the recommendations?
-     */
+    /** Are there any resume media notifications active, excluding the recommendations? */
     fun hasActiveMedia() = mediaDataFilter.hasActiveMedia()
 
     /**
-    * Are there any resume media notifications active, excluding the recommendations?
-    * If resumption is enabled, this will include inactive players
-    * If resumption is disabled, we only want to show active players
-    */
+     * Are there any resume media notifications active, excluding the recommendations?
+     * - If resumption is enabled, this will include inactive players
+     * - If resumption is disabled, we only want to show active players
+     */
     fun hasAnyMedia() = mediaDataFilter.hasAnyMedia()
 
     interface Listener {
@@ -1275,10 +1402,9 @@
     ): SmartspaceMediaData {
         var dismissIntent: Intent? = null
         if (target.baseAction != null && target.baseAction.extras != null) {
-            dismissIntent = target
-                .baseAction
-                .extras
-                .getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY) as Intent?
+            dismissIntent =
+                target.baseAction.extras.getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY)
+                    as Intent?
         }
         packageName(target)?.let {
             return SmartspaceMediaData(
@@ -1289,14 +1415,16 @@
                 recommendations = target.iconGrid,
                 dismissIntent = dismissIntent,
                 headphoneConnectionTimeMillis = target.creationTimeMillis,
-                instanceId = logger.getNewInstanceId())
+                instanceId = logger.getNewInstanceId()
+            )
         }
-        return EMPTY_SMARTSPACE_MEDIA_DATA
-            .copy(targetId = target.smartspaceTargetId,
-                    isActive = isActive,
-                    dismissIntent = dismissIntent,
-                    headphoneConnectionTimeMillis = target.creationTimeMillis,
-                    instanceId = logger.getNewInstanceId())
+        return EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+            targetId = target.smartspaceTargetId,
+            isActive = isActive,
+            dismissIntent = dismissIntent,
+            headphoneConnectionTimeMillis = target.creationTimeMillis,
+            instanceId = logger.getNewInstanceId()
+        )
     }
 
     private fun packageName(target: SmartspaceTarget): String? {
@@ -1308,8 +1436,9 @@
         for (recommendation in recommendationList) {
             val extras = recommendation.extras
             extras?.let {
-                it.getString(EXTRAS_MEDIA_SOURCE_PACKAGE_NAME)?.let {
-                    packageName -> return packageName }
+                it.getString(EXTRAS_MEDIA_SOURCE_PACKAGE_NAME)?.let { packageName ->
+                    return packageName
+                }
             }
         }
         Log.w(TAG, "No valid package name is provided.")
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
similarity index 70%
rename from packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
index b3a4ddf..6a512be 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.pipeline
 
 import android.bluetooth.BluetoothLeBroadcast
 import android.bluetooth.BluetoothLeBroadcastMetadata
@@ -36,6 +36,10 @@
 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.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaDataUtils
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -47,10 +51,10 @@
 private const val TAG = "MediaDeviceManager"
 private const val DEBUG = true
 
-/**
- * Provides information about the route (ie. device) where playback is occurring.
- */
-class MediaDeviceManager @Inject constructor(
+/** Provides information about the route (ie. device) where playback is occurring. */
+class MediaDeviceManager
+@Inject
+constructor(
     private val context: Context,
     private val controllerFactory: MediaControllerFactory,
     private val localMediaManagerFactory: LocalMediaManagerFactory,
@@ -70,14 +74,10 @@
         dumpManager.registerDumpable(javaClass.name, this)
     }
 
-    /**
-     * Add a listener for changes to the media route (ie. device).
-     */
+    /** Add a listener for changes to the media route (ie. device). */
     fun addListener(listener: Listener) = listeners.add(listener)
 
-    /**
-     * Remove a listener that has been registered with addListener.
-     */
+    /** Remove a listener that has been registered with addListener. */
     fun removeListener(listener: Listener) = listeners.remove(listener)
 
     override fun onMediaDataLoaded(
@@ -101,19 +101,11 @@
                 processDevice(key, oldKey, data.device)
                 return
             }
-            val controller = data.token?.let {
-                controllerFactory.create(it)
-            }
+            val controller = data.token?.let { controllerFactory.create(it) }
             val localMediaManager = localMediaManagerFactory.create(data.packageName)
             val muteAwaitConnectionManager =
-                    muteAwaitConnectionManagerFactory.create(localMediaManager)
-            entry = Entry(
-                key,
-                oldKey,
-                controller,
-                localMediaManager,
-                muteAwaitConnectionManager
-            )
+                muteAwaitConnectionManagerFactory.create(localMediaManager)
+            entry = Entry(key, oldKey, controller, localMediaManager, muteAwaitConnectionManager)
             entries[key] = entry
             entry.start()
         }
@@ -122,11 +114,7 @@
     override fun onMediaDataRemoved(key: String) {
         val token = entries.remove(key)
         token?.stop()
-        token?.let {
-            listeners.forEach {
-                it.onKeyRemoved(key)
-            }
-        }
+        token?.let { listeners.forEach { it.onKeyRemoved(key) } }
     }
 
     override fun dump(pw: PrintWriter, args: Array<String>) {
@@ -141,9 +129,7 @@
 
     @MainThread
     private fun processDevice(key: String, oldKey: String?, device: MediaDeviceData?) {
-        listeners.forEach {
-            it.onMediaDeviceChanged(key, oldKey, device)
-        }
+        listeners.forEach { it.onMediaDeviceChanged(key, oldKey, device) }
     }
 
     interface Listener {
@@ -159,8 +145,10 @@
         val controller: MediaController?,
         val localMediaManager: LocalMediaManager,
         val muteAwaitConnectionManager: MediaMuteAwaitConnectionManager?
-    ) : LocalMediaManager.DeviceCallback, MediaController.Callback(),
-            BluetoothLeBroadcast.Callback {
+    ) :
+        LocalMediaManager.DeviceCallback,
+        MediaController.Callback(),
+        BluetoothLeBroadcast.Callback {
 
         val token
             get() = controller?.sessionToken
@@ -171,54 +159,52 @@
                 val sameWithoutIcon = value != null && value.equalsWithoutIcon(field)
                 if (!started || !sameWithoutIcon) {
                     field = value
-                    fgExecutor.execute {
-                        processDevice(key, oldKey, value)
-                    }
+                    fgExecutor.execute { processDevice(key, oldKey, value) }
                 }
             }
         // A device that is not yet connected but is expected to connect imminently. Because it's
         // expected to connect imminently, it should be displayed as the current device.
         private var aboutToConnectDeviceOverride: AboutToConnectDevice? = null
         private var broadcastDescription: String? = null
-        private val configListener = object : ConfigurationController.ConfigurationListener {
-            override fun onLocaleListChanged() {
-                updateCurrent()
+        private val configListener =
+            object : ConfigurationController.ConfigurationListener {
+                override fun onLocaleListChanged() {
+                    updateCurrent()
+                }
             }
-        }
 
         @AnyThread
-        fun start() = bgExecutor.execute {
-            if (!started) {
-                localMediaManager.registerCallback(this)
-                localMediaManager.startScan()
-                muteAwaitConnectionManager?.startListening()
-                playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
-                controller?.registerCallback(this)
-                updateCurrent()
-                started = true
-                configurationController.addCallback(configListener)
+        fun start() =
+            bgExecutor.execute {
+                if (!started) {
+                    localMediaManager.registerCallback(this)
+                    localMediaManager.startScan()
+                    muteAwaitConnectionManager?.startListening()
+                    playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
+                    controller?.registerCallback(this)
+                    updateCurrent()
+                    started = true
+                    configurationController.addCallback(configListener)
+                }
             }
-        }
 
         @AnyThread
-        fun stop() = bgExecutor.execute {
-            if (started) {
-                started = false
-                controller?.unregisterCallback(this)
-                localMediaManager.stopScan()
-                localMediaManager.unregisterCallback(this)
-                muteAwaitConnectionManager?.stopListening()
-                configurationController.removeCallback(configListener)
+        fun stop() =
+            bgExecutor.execute {
+                if (started) {
+                    started = false
+                    controller?.unregisterCallback(this)
+                    localMediaManager.stopScan()
+                    localMediaManager.unregisterCallback(this)
+                    muteAwaitConnectionManager?.stopListening()
+                    configurationController.removeCallback(configListener)
+                }
             }
-        }
 
         fun dump(pw: PrintWriter) {
-            val routingSession = controller?.let {
-                mr2manager.getRoutingSessionForMediaController(it)
-            }
-            val selectedRoutes = routingSession?.let {
-                mr2manager.getSelectedRoutes(it)
-            }
+            val routingSession =
+                controller?.let { mr2manager.getRoutingSessionForMediaController(it) }
+            val selectedRoutes = routingSession?.let { mr2manager.getSelectedRoutes(it) }
             with(pw) {
                 println("    current device is ${current?.name}")
                 val type = controller?.playbackInfo?.playbackType
@@ -238,14 +224,11 @@
             updateCurrent()
         }
 
-        override fun onDeviceListUpdate(devices: List<MediaDevice>?) = bgExecutor.execute {
-            updateCurrent()
-        }
+        override fun onDeviceListUpdate(devices: List<MediaDevice>?) =
+            bgExecutor.execute { updateCurrent() }
 
         override fun onSelectedDeviceStateChanged(device: MediaDevice, state: Int) {
-            bgExecutor.execute {
-                updateCurrent()
-            }
+            bgExecutor.execute { updateCurrent() }
         }
 
         override fun onAboutToConnectDeviceAdded(
@@ -253,14 +236,17 @@
             deviceName: String,
             deviceIcon: Drawable?
         ) {
-            aboutToConnectDeviceOverride = AboutToConnectDevice(
-                fullMediaDevice = localMediaManager.getMediaDeviceById(deviceAddress),
-                backupMediaDeviceData = MediaDeviceData(
-                        /* enabled */ enabled = true,
-                        /* icon */ deviceIcon,
-                        /* name */ deviceName,
-                        /* showBroadcastButton */ showBroadcastButton = false)
-            )
+            aboutToConnectDeviceOverride =
+                AboutToConnectDevice(
+                    fullMediaDevice = localMediaManager.getMediaDeviceById(deviceAddress),
+                    backupMediaDeviceData =
+                        MediaDeviceData(
+                            /* enabled */ enabled = true,
+                            /* icon */ deviceIcon,
+                            /* name */ deviceName,
+                            /* showBroadcastButton */ showBroadcastButton = false
+                        )
+                )
             updateCurrent()
         }
 
@@ -287,8 +273,11 @@
             metadata: BluetoothLeBroadcastMetadata
         ) {
             if (DEBUG) {
-                Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = $broadcastId , " +
-                        "metadata = $metadata")
+                Log.d(
+                    TAG,
+                    "onBroadcastMetadataChanged(), broadcastId = $broadcastId , " +
+                        "metadata = $metadata"
+                )
             }
             updateCurrent()
         }
@@ -315,8 +304,10 @@
 
         override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {
             if (DEBUG) {
-                Log.d(TAG, "onBroadcastUpdateFailed(), reason = $reason , " +
-                        "broadcastId = $broadcastId")
+                Log.d(
+                    TAG,
+                    "onBroadcastUpdateFailed(), reason = $reason , " + "broadcastId = $broadcastId"
+                )
             }
         }
 
@@ -327,34 +318,45 @@
         @WorkerThread
         private fun updateCurrent() {
             if (isLeAudioBroadcastEnabled()) {
-                current = MediaDeviceData(
+                current =
+                    MediaDeviceData(
                         /* enabled */ true,
                         /* icon */ context.getDrawable(R.drawable.settings_input_antenna),
                         /* name */ broadcastDescription,
                         /* intent */ null,
-                        /* showBroadcastButton */ showBroadcastButton = true)
+                        /* showBroadcastButton */ showBroadcastButton = true
+                    )
             } else {
                 val aboutToConnect = aboutToConnectDeviceOverride
-                if (aboutToConnect != null &&
+                if (
+                    aboutToConnect != null &&
                         aboutToConnect.fullMediaDevice == null &&
-                        aboutToConnect.backupMediaDeviceData != null) {
+                        aboutToConnect.backupMediaDeviceData != null
+                ) {
                     // Only use [backupMediaDeviceData] when we don't have [fullMediaDevice].
                     current = aboutToConnect.backupMediaDeviceData
                     return
                 }
-                val device = aboutToConnect?.fullMediaDevice
-                        ?: localMediaManager.currentConnectedDevice
+                val device =
+                    aboutToConnect?.fullMediaDevice ?: localMediaManager.currentConnectedDevice
                 val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) }
 
                 // If we have a controller but get a null route, then don't trust the device
                 val enabled = device != null && (controller == null || route != null)
-                val name = if (controller == null || route != null) {
-                    route?.name?.toString() ?: device?.name
-                } else {
-                    null
-                }
-                current = MediaDeviceData(enabled, device?.iconWithoutBackground, name,
-                        id = device?.id, showBroadcastButton = false)
+                val name =
+                    if (controller == null || route != null) {
+                        route?.name?.toString() ?: device?.name
+                    } else {
+                        null
+                    }
+                current =
+                    MediaDeviceData(
+                        enabled,
+                        device?.iconWithoutBackground,
+                        name,
+                        id = device?.id,
+                        showBroadcastButton = false
+                    )
             }
         }
 
@@ -384,13 +386,16 @@
             // unexpected result.
             // Check the current media app's name is the same with current broadcast app's name
             // or not.
-            var mediaApp = MediaDataUtils.getAppLabel(
-                    context, localMediaManager.packageName,
-                    context.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name))
+            var mediaApp =
+                MediaDataUtils.getAppLabel(
+                    context,
+                    localMediaManager.packageName,
+                    context.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name)
+                )
             var isCurrentBroadcastedApp = TextUtils.equals(mediaApp, currentBroadcastedApp)
             if (isCurrentBroadcastedApp) {
-                broadcastDescription = context.getString(
-                        R.string.broadcasting_description_is_broadcasting)
+                broadcastDescription =
+                    context.getString(R.string.broadcasting_description_is_broadcasting)
             } else {
                 broadcastDescription = currentBroadcastedApp
             }
@@ -403,9 +408,9 @@
  * [LocalMediaManager.DeviceCallback.onAboutToConnectDeviceAdded] for more information.
  *
  * @property fullMediaDevice a full-fledged [MediaDevice] object representing the device. If
- *   non-null, prefer using [fullMediaDevice] over [backupMediaDeviceData].
+ * non-null, prefer using [fullMediaDevice] over [backupMediaDeviceData].
  * @property backupMediaDeviceData a backup [MediaDeviceData] object containing the minimum
- *   information required to display the device. Only use if [fullMediaDevice] is null.
+ * information required to display the device. Only use if [fullMediaDevice] is null.
  */
 private data class AboutToConnectDevice(
     val fullMediaDevice: MediaDevice? = null,
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
similarity index 78%
rename from packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
index 3179296..ab93b29 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.pipeline
 
 import android.content.ComponentName
 import android.content.Context
@@ -25,6 +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.statusbar.phone.NotificationListenerWithPlugins
 import java.util.concurrent.Executor
 import javax.inject.Inject
@@ -38,7 +40,9 @@
  * sessions. In this situation, there should only be a media object for the remote session. To
  * achieve this, update events for the local session need to be filtered.
  */
-class MediaSessionBasedFilter @Inject constructor(
+class MediaSessionBasedFilter
+@Inject
+constructor(
     context: Context,
     private val sessionManager: MediaSessionManager,
     @Main private val foregroundExecutor: Executor,
@@ -50,7 +54,7 @@
     // Keep track of MediaControllers for a given package to check if an app is casting and it
     // filter loaded events for local sessions.
     private val packageControllers: LinkedHashMap<String, MutableList<MediaController>> =
-            LinkedHashMap()
+        LinkedHashMap()
 
     // Keep track of the key used for the session tokens. This information is used to know when to
     // dispatch a removed event so that a media object for a local session will be removed.
@@ -59,11 +63,12 @@
     // Keep track of which media session tokens have associated notifications.
     private val tokensWithNotifications: MutableSet<MediaSession.Token> = mutableSetOf()
 
-    private val sessionListener = object : MediaSessionManager.OnActiveSessionsChangedListener {
-        override fun onActiveSessionsChanged(controllers: List<MediaController>) {
-            handleControllersChanged(controllers)
+    private val sessionListener =
+        object : MediaSessionManager.OnActiveSessionsChangedListener {
+            override fun onActiveSessionsChanged(controllers: List<MediaController>) {
+                handleControllersChanged(controllers)
+            }
         }
-    }
 
     init {
         backgroundExecutor.execute {
@@ -73,14 +78,10 @@
         }
     }
 
-    /**
-     * Add a listener for filtered [MediaData] changes
-     */
+    /** Add a listener for filtered [MediaData] changes */
     fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener)
 
-    /**
-     * Remove a listener that was registered with addListener
-     */
+    /** Remove a listener that was registered with addListener */
     fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)
 
     /**
@@ -100,31 +101,32 @@
         isSsReactivated: Boolean
     ) {
         backgroundExecutor.execute {
-            data.token?.let {
-                tokensWithNotifications.add(it)
-            }
+            data.token?.let { tokensWithNotifications.add(it) }
             val isMigration = oldKey != null && key != oldKey
             if (isMigration) {
                 keyedTokens.remove(oldKey)?.let { removed -> keyedTokens.put(key, removed) }
             }
             if (data.token != null) {
-                keyedTokens.get(key)?.let {
-                    tokens ->
-                    tokens.add(data.token)
-                } ?: run {
-                    val tokens = mutableSetOf(data.token)
-                    keyedTokens.put(key, tokens)
-                }
+                keyedTokens.get(key)?.let { tokens -> tokens.add(data.token) }
+                    ?: run {
+                        val tokens = mutableSetOf(data.token)
+                        keyedTokens.put(key, tokens)
+                    }
             }
             // Determine if an app is casting by checking if it has a session with playback type
             // PLAYBACK_TYPE_REMOTE.
-            val remoteControllers = packageControllers.get(data.packageName)?.filter {
-                it.playbackInfo?.playbackType == PlaybackInfo.PLAYBACK_TYPE_REMOTE
-            }
+            val remoteControllers =
+                packageControllers.get(data.packageName)?.filter {
+                    it.playbackInfo?.playbackType == PlaybackInfo.PLAYBACK_TYPE_REMOTE
+                }
             // Limiting search to only apps with a single remote session.
             val remote = if (remoteControllers?.size == 1) remoteControllers.firstOrNull() else null
-            if (isMigration || remote == null || remote.sessionToken == data.token ||
-                    !tokensWithNotifications.contains(remote.sessionToken)) {
+            if (
+                isMigration ||
+                    remote == null ||
+                    remote.sessionToken == data.token ||
+                    !tokensWithNotifications.contains(remote.sessionToken)
+            ) {
                 // Not filtering in this case. Passing the event along to listeners.
                 dispatchMediaDataLoaded(key, oldKey, data, immediately)
             } else {
@@ -146,9 +148,7 @@
         data: SmartspaceMediaData,
         shouldPrioritize: Boolean
     ) {
-        backgroundExecutor.execute {
-            dispatchSmartspaceMediaDataLoaded(key, data)
-        }
+        backgroundExecutor.execute { dispatchSmartspaceMediaDataLoaded(key, data) }
     }
 
     override fun onMediaDataRemoved(key: String) {
@@ -160,9 +160,7 @@
     }
 
     override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
-        backgroundExecutor.execute {
-            dispatchSmartspaceMediaDataRemoved(key, immediately)
-        }
+        backgroundExecutor.execute { dispatchSmartspaceMediaDataRemoved(key, immediately) }
     }
 
     private fun dispatchMediaDataLoaded(
@@ -177,9 +175,7 @@
     }
 
     private fun dispatchMediaDataRemoved(key: String) {
-        foregroundExecutor.execute {
-            listeners.toSet().forEach { it.onMediaDataRemoved(key) }
-        }
+        foregroundExecutor.execute { listeners.toSet().forEach { it.onMediaDataRemoved(key) } }
     }
 
     private fun dispatchSmartspaceMediaDataLoaded(key: String, info: SmartspaceMediaData) {
@@ -196,15 +192,12 @@
 
     private fun handleControllersChanged(controllers: List<MediaController>) {
         packageControllers.clear()
-        controllers.forEach {
-            controller ->
-            packageControllers.get(controller.packageName)?.let {
-                tokens ->
-                tokens.add(controller)
-            } ?: run {
-                val tokens = mutableListOf(controller)
-                packageControllers.put(controller.packageName, tokens)
-            }
+        controllers.forEach { controller ->
+            packageControllers.get(controller.packageName)?.let { tokens -> tokens.add(controller) }
+                ?: run {
+                    val tokens = mutableListOf(controller)
+                    packageControllers.put(controller.packageName, tokens)
+                }
         }
         tokensWithNotifications.retainAll(controllers.map { it.sessionToken })
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
index 93a29ef..7f5c82f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.pipeline
 
 import android.media.session.MediaController
 import android.media.session.PlaybackState
@@ -22,6 +22,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.util.MediaControllerFactory
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -31,18 +33,18 @@
 import javax.inject.Inject
 
 @VisibleForTesting
-val PAUSED_MEDIA_TIMEOUT = SystemProperties
-        .getLong("debug.sysui.media_timeout", TimeUnit.MINUTES.toMillis(10))
+val PAUSED_MEDIA_TIMEOUT =
+    SystemProperties.getLong("debug.sysui.media_timeout", TimeUnit.MINUTES.toMillis(10))
 
 @VisibleForTesting
-val RESUME_MEDIA_TIMEOUT = SystemProperties
-        .getLong("debug.sysui.media_timeout_resume", TimeUnit.DAYS.toMillis(3))
+val RESUME_MEDIA_TIMEOUT =
+    SystemProperties.getLong("debug.sysui.media_timeout_resume", TimeUnit.DAYS.toMillis(3))
 
-/**
- * Controller responsible for keeping track of playback states and expiring inactive streams.
- */
+/** Controller responsible for keeping track of playback states and expiring inactive streams. */
 @SysUISingleton
-class MediaTimeoutListener @Inject constructor(
+class MediaTimeoutListener
+@Inject
+constructor(
     private val mediaControllerFactory: MediaControllerFactory,
     @Main private val mainExecutor: DelayableExecutor,
     private val logger: MediaTimeoutLogger,
@@ -56,7 +58,9 @@
      * Callback representing that a media object is now expired:
      * @param key Media control unique identifier
      * @param timedOut True when expired for {@code PAUSED_MEDIA_TIMEOUT} for active media,
+     * ```
      *                 or {@code RESUME_MEDIA_TIMEOUT} for resume media
+     * ```
      */
     lateinit var timeoutCallback: (String, Boolean) -> Unit
 
@@ -68,21 +72,25 @@
     lateinit var stateCallback: (String, PlaybackState) -> Unit
 
     init {
-        statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
-            override fun onDozingChanged(isDozing: Boolean) {
-                if (!isDozing) {
-                    // Check whether any timeouts should have expired
-                    mediaListeners.forEach { (key, listener) ->
-                        if (listener.cancellation != null &&
-                                listener.expiration <= systemClock.elapsedRealtime()) {
-                            // We dozed too long - timeout now, and cancel the pending one
-                            listener.expireMediaTimeout(key, "timeout happened while dozing")
-                            listener.doTimeout()
+        statusBarStateController.addCallback(
+            object : StatusBarStateController.StateListener {
+                override fun onDozingChanged(isDozing: Boolean) {
+                    if (!isDozing) {
+                        // Check whether any timeouts should have expired
+                        mediaListeners.forEach { (key, listener) ->
+                            if (
+                                listener.cancellation != null &&
+                                    listener.expiration <= systemClock.elapsedRealtime()
+                            ) {
+                                // We dozed too long - timeout now, and cancel the pending one
+                                listener.expireMediaTimeout(key, "timeout happened while dozing")
+                                listener.doTimeout()
+                            }
                         }
                     }
                 }
             }
-        })
+        )
     }
 
     override fun onMediaDataLoaded(
@@ -145,10 +153,8 @@
         return mediaListeners[key]?.timedOut ?: false
     }
 
-    private inner class PlaybackStateListener(
-        var key: String,
-        data: MediaData
-    ) : MediaController.Callback() {
+    private inner class PlaybackStateListener(var key: String, data: MediaData) :
+        MediaController.Callback() {
 
         var timedOut = false
         var lastState: PlaybackState? = null
@@ -162,11 +168,12 @@
                 mediaController?.unregisterCallback(this)
                 field = value
                 val token = field.token
-                mediaController = if (token != null) {
-                    mediaControllerFactory.create(token)
-                } else {
-                    null
-                }
+                mediaController =
+                    if (token != null) {
+                        mediaControllerFactory.create(token)
+                    } else {
+                        null
+                    }
                 mediaController?.registerCallback(this)
                 // Let's register the cancellations, but not dispatch events now.
                 // Timeouts didn't happen yet and reentrant events are troublesome.
@@ -212,7 +219,8 @@
             logger.logPlaybackState(key, state)
 
             val playingStateSame = (state?.state?.isPlaying() == isPlaying())
-            val actionsSame = (lastState?.actions == state?.actions) &&
+            val actionsSame =
+                (lastState?.actions == state?.actions) &&
                     areCustomActionListsEqual(lastState?.customActions, state?.customActions)
             val resumptionChanged = resumption != mediaData.resumption
 
@@ -237,15 +245,14 @@
                     return
                 }
                 expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state, $resumption")
-                val timeout = if (mediaData.resumption) {
-                    RESUME_MEDIA_TIMEOUT
-                } else {
-                    PAUSED_MEDIA_TIMEOUT
-                }
+                val timeout =
+                    if (mediaData.resumption) {
+                        RESUME_MEDIA_TIMEOUT
+                    } else {
+                        PAUSED_MEDIA_TIMEOUT
+                    }
                 expiration = systemClock.elapsedRealtime() + timeout
-                cancellation = mainExecutor.executeDelayed({
-                    doTimeout()
-                }, timeout)
+                cancellation = mainExecutor.executeDelayed({ doTimeout() }, timeout)
             } else {
                 expireMediaTimeout(key, "playback started - $state, $key")
                 timedOut = false
@@ -301,9 +308,11 @@
         firstAction: PlaybackState.CustomAction,
         secondAction: PlaybackState.CustomAction
     ): Boolean {
-        if (firstAction.action != secondAction.action ||
+        if (
+            firstAction.action != secondAction.action ||
                 firstAction.name != secondAction.name ||
-                firstAction.icon != secondAction.icon) {
+                firstAction.icon != secondAction.icon
+        ) {
             return false
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
new file mode 100644
index 0000000..8f3f054
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.media.controls.pipeline
+
+import android.media.session.PlaybackState
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.MediaTimeoutListenerLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import javax.inject.Inject
+
+private const val TAG = "MediaTimeout"
+
+/** A buffered log for [MediaTimeoutListener] events */
+@SysUISingleton
+class MediaTimeoutLogger
+@Inject
+constructor(@MediaTimeoutListenerLog private val buffer: LogBuffer) {
+    fun logReuseListener(key: String) =
+        buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "reuse listener: $str1" })
+
+    fun logMigrateListener(oldKey: String?, newKey: String?, hadListener: Boolean) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = oldKey
+                str2 = newKey
+                bool1 = hadListener
+            },
+            { "migrate from $str1 to $str2, had listener? $bool1" }
+        )
+
+    fun logUpdateListener(key: String, wasPlaying: Boolean) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = key
+                bool1 = wasPlaying
+            },
+            { "updating $str1, was playing? $bool1" }
+        )
+
+    fun logDelayedUpdate(key: String) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { str1 = key },
+            { "deliver delayed playback state for $str1" }
+        )
+
+    fun logSessionDestroyed(key: String) =
+        buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "session destroyed $str1" })
+
+    fun logPlaybackState(key: String, state: PlaybackState?) =
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            {
+                str1 = key
+                str2 = state?.toString()
+            },
+            { "state update: key=$str1 state=$str2" }
+        )
+
+    fun logStateCallback(key: String) =
+        buffer.log(TAG, LogLevel.VERBOSE, { str1 = key }, { "dispatching state update for $key" })
+
+    fun logScheduleTimeout(key: String, playing: Boolean, resumption: Boolean) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = key
+                bool1 = playing
+                bool2 = resumption
+            },
+            { "schedule timeout $str1, playing=$bool1 resumption=$bool2" }
+        )
+
+    fun logCancelIgnored(key: String) =
+        buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "cancellation already exists for $str1" })
+
+    fun logTimeout(key: String) =
+        buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "execute timeout for $str1" })
+
+    fun logTimeoutCancelled(key: String, reason: String) =
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            {
+                str1 = key
+                str2 = reason
+            },
+            { "media timeout cancelled for $str1, reason: $str2" }
+        )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaBrowserFactory.java b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaBrowserFactory.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/media/MediaBrowserFactory.java
rename to packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaBrowserFactory.java
index aca033e..00620b5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaBrowserFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaBrowserFactory.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media;
+package com.android.systemui.media.controls.resume;
 
 import android.content.ComponentName;
 import android.content.Context;
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
index cc06b6c..4891297 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.resume
 
 import android.content.BroadcastReceiver
 import android.content.ComponentName
@@ -33,6 +33,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 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.tuner.TunerService
 import com.android.systemui.util.Utils
 import com.android.systemui.util.time.SystemClock
@@ -47,7 +50,9 @@
 private const val MEDIA_PREFERENCE_KEY = "browser_components_"
 
 @SysUISingleton
-class MediaResumeListener @Inject constructor(
+class MediaResumeListener
+@Inject
+constructor(
     private val context: Context,
     private val broadcastDispatcher: BroadcastDispatcher,
     @Background private val backgroundExecutor: Executor,
@@ -59,7 +64,7 @@
 
     private var useMediaResumption: Boolean = Utils.useMediaResumption(context)
     private val resumeComponents: ConcurrentLinkedQueue<Pair<ComponentName, Long>> =
-            ConcurrentLinkedQueue()
+        ConcurrentLinkedQueue()
 
     private lateinit var mediaDataManager: MediaDataManager
 
@@ -72,40 +77,49 @@
     private var currentUserId: Int = context.userId
 
     @VisibleForTesting
-    val userChangeReceiver = object : BroadcastReceiver() {
-        override fun onReceive(context: Context, intent: Intent) {
-            if (Intent.ACTION_USER_UNLOCKED == intent.action) {
-                loadMediaResumptionControls()
-            } else if (Intent.ACTION_USER_SWITCHED == intent.action) {
-                currentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)
-                loadSavedComponents()
+    val userChangeReceiver =
+        object : BroadcastReceiver() {
+            override fun onReceive(context: Context, intent: Intent) {
+                if (Intent.ACTION_USER_UNLOCKED == intent.action) {
+                    loadMediaResumptionControls()
+                } else if (Intent.ACTION_USER_SWITCHED == intent.action) {
+                    currentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)
+                    loadSavedComponents()
+                }
             }
         }
-    }
 
-    private val mediaBrowserCallback = object : ResumeMediaBrowser.Callback() {
-        override fun addTrack(
-            desc: MediaDescription,
-            component: ComponentName,
-            browser: ResumeMediaBrowser
-        ) {
-            val token = browser.token
-            val appIntent = browser.appIntent
-            val pm = context.getPackageManager()
-            var appName: CharSequence = component.packageName
-            val resumeAction = getResumeAction(component)
-            try {
-                appName = pm.getApplicationLabel(
-                        pm.getApplicationInfo(component.packageName, 0))
-            } catch (e: PackageManager.NameNotFoundException) {
-                Log.e(TAG, "Error getting package information", e)
+    private val mediaBrowserCallback =
+        object : ResumeMediaBrowser.Callback() {
+            override fun addTrack(
+                desc: MediaDescription,
+                component: ComponentName,
+                browser: ResumeMediaBrowser
+            ) {
+                val token = browser.token
+                val appIntent = browser.appIntent
+                val pm = context.getPackageManager()
+                var appName: CharSequence = component.packageName
+                val resumeAction = getResumeAction(component)
+                try {
+                    appName =
+                        pm.getApplicationLabel(pm.getApplicationInfo(component.packageName, 0))
+                } catch (e: PackageManager.NameNotFoundException) {
+                    Log.e(TAG, "Error getting package information", e)
+                }
+
+                Log.d(TAG, "Adding resume controls $desc")
+                mediaDataManager.addResumptionControls(
+                    currentUserId,
+                    desc,
+                    resumeAction,
+                    token,
+                    appName.toString(),
+                    appIntent,
+                    component.packageName
+                )
             }
-
-            Log.d(TAG, "Adding resume controls $desc")
-            mediaDataManager.addResumptionControls(currentUserId, desc, resumeAction, token,
-                appName.toString(), appIntent, component.packageName)
         }
-    }
 
     init {
         if (useMediaResumption) {
@@ -113,8 +127,12 @@
             val unlockFilter = IntentFilter()
             unlockFilter.addAction(Intent.ACTION_USER_UNLOCKED)
             unlockFilter.addAction(Intent.ACTION_USER_SWITCHED)
-            broadcastDispatcher.registerReceiver(userChangeReceiver, unlockFilter, null,
-                UserHandle.ALL)
+            broadcastDispatcher.registerReceiver(
+                userChangeReceiver,
+                unlockFilter,
+                null,
+                UserHandle.ALL
+            )
             loadSavedComponents()
         }
     }
@@ -123,12 +141,15 @@
         mediaDataManager = manager
 
         // Add listener for resumption setting changes
-        tunerService.addTunable(object : TunerService.Tunable {
-            override fun onTuningChanged(key: String?, newValue: String?) {
-                useMediaResumption = Utils.useMediaResumption(context)
-                mediaDataManager.setMediaResumptionEnabled(useMediaResumption)
-            }
-        }, Settings.Secure.MEDIA_CONTROLS_RESUME)
+        tunerService.addTunable(
+            object : TunerService.Tunable {
+                override fun onTuningChanged(key: String?, newValue: String?) {
+                    useMediaResumption = Utils.useMediaResumption(context)
+                    mediaDataManager.setMediaResumptionEnabled(useMediaResumption)
+                }
+            },
+            Settings.Secure.MEDIA_CONTROLS_RESUME
+        )
     }
 
     private fun loadSavedComponents() {
@@ -136,8 +157,10 @@
         resumeComponents.clear()
         val prefs = context.getSharedPreferences(MEDIA_PREFERENCES, Context.MODE_PRIVATE)
         val listString = prefs.getString(MEDIA_PREFERENCE_KEY + currentUserId, null)
-        val components = listString?.split(ResumeMediaBrowser.DELIMITER.toRegex())
-            ?.dropLastWhile { it.isEmpty() }
+        val components =
+            listString?.split(ResumeMediaBrowser.DELIMITER.toRegex())?.dropLastWhile {
+                it.isEmpty()
+            }
         var needsUpdate = false
         components?.forEach {
             val info = it.split("/")
@@ -145,17 +168,18 @@
             val className = info[1]
             val component = ComponentName(packageName, className)
 
-            val lastPlayed = if (info.size == 3) {
-                try {
-                    info[2].toLong()
-                } catch (e: NumberFormatException) {
+            val lastPlayed =
+                if (info.size == 3) {
+                    try {
+                        info[2].toLong()
+                    } catch (e: NumberFormatException) {
+                        needsUpdate = true
+                        systemClock.currentTimeMillis()
+                    }
+                } else {
                     needsUpdate = true
                     systemClock.currentTimeMillis()
                 }
-            } else {
-                needsUpdate = true
-                systemClock.currentTimeMillis()
-            }
             resumeComponents.add(component to lastPlayed)
         }
         Log.d(TAG, "loaded resume components ${resumeComponents.toArray().contentToString()}")
@@ -166,9 +190,7 @@
         }
     }
 
-    /**
-     * Load controls for resuming media, if available
-     */
+    /** Load controls for resuming media, if available */
     private fun loadMediaResumptionControls() {
         if (!useMediaResumption) {
             return
@@ -204,9 +226,7 @@
                 val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE)
                 val resumeInfo = pm.queryIntentServices(serviceIntent, 0)
 
-                val inf = resumeInfo?.filter {
-                    it.serviceInfo.packageName == data.packageName
-                }
+                val inf = resumeInfo?.filter { it.serviceInfo.packageName == data.packageName }
                 if (inf != null && inf.size > 0) {
                     backgroundExecutor.execute {
                         tryUpdateResumptionList(key, inf!!.get(0).componentInfo.componentName)
@@ -227,7 +247,8 @@
         Log.d(TAG, "Testing if we can connect to $componentName")
         // Set null action to prevent additional attempts to connect
         mediaDataManager.setResumeAction(key, null)
-        mediaBrowser = mediaBrowserFactory.create(
+        mediaBrowser =
+            mediaBrowserFactory.create(
                 object : ResumeMediaBrowser.Callback() {
                     override fun onConnected() {
                         Log.d(TAG, "Connected to $componentName")
@@ -250,7 +271,8 @@
                         mediaBrowser = null
                     }
                 },
-                componentName)
+                componentName
+            )
         mediaBrowser?.testConnection()
     }
 
@@ -285,9 +307,7 @@
         prefs.edit().putString(MEDIA_PREFERENCE_KEY + currentUserId, sb.toString()).apply()
     }
 
-    /**
-     * Get a runnable which will resume media playback
-     */
+    /** Get a runnable which will resume media playback */
     private fun getResumeAction(componentName: ComponentName): Runnable {
         return Runnable {
             mediaBrowser = mediaBrowserFactory.create(null, componentName)
@@ -296,8 +316,6 @@
     }
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
-        pw.apply {
-            println("resumeComponents: $resumeComponents")
-        }
+        pw.apply { println("resumeComponents: $resumeComponents") }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
rename to packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
index 40a5653..3493b24 100644
--- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media;
+package com.android.systemui.media.controls.resume;
 
 import android.annotation.Nullable;
 import android.app.PendingIntent;
@@ -293,7 +293,7 @@
     public PendingIntent getAppIntent() {
         PackageManager pm = mContext.getPackageManager();
         Intent launchIntent = pm.getLaunchIntentForPackage(mComponentName.getPackageName());
-        return PendingIntent.getActivity(mContext, 0, launchIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        return PendingIntent.getActivity(mContext, 0, launchIntent, PendingIntent.FLAG_IMMUTABLE);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java
rename to packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java
index 3d1380b..c558227 100644
--- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media;
+package com.android.systemui.media.controls.resume;
 
 import android.content.ComponentName;
 import android.content.Context;
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt
new file mode 100644
index 0000000..335ce1d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.media.controls.resume
+
+import android.content.ComponentName
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.MediaBrowserLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import javax.inject.Inject
+
+/** A logger for events in [ResumeMediaBrowser]. */
+@SysUISingleton
+class ResumeMediaBrowserLogger @Inject constructor(@MediaBrowserLog private val buffer: LogBuffer) {
+    /** Logs that we've initiated a connection to a [android.media.browse.MediaBrowser]. */
+    fun logConnection(componentName: ComponentName, reason: String) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = componentName.toShortString()
+                str2 = reason
+            },
+            { "Connecting browser for component $str1 due to $str2" }
+        )
+
+    /** Logs that we've disconnected from a [android.media.browse.MediaBrowser]. */
+    fun logDisconnect(componentName: ComponentName) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { str1 = componentName.toShortString() },
+            { "Disconnecting browser for component $str1" }
+        )
+
+    /**
+     * Logs that we received a [android.media.session.MediaController.Callback.onSessionDestroyed]
+     * event.
+     *
+     * @param isBrowserConnected true if there's a currently connected
+     * ```
+     *     [android.media.browse.MediaBrowser] and false otherwise.
+     * @param componentName
+     * ```
+     * the component name for the [ResumeMediaBrowser] that triggered this log.
+     */
+    fun logSessionDestroyed(isBrowserConnected: Boolean, componentName: ComponentName) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                bool1 = isBrowserConnected
+                str1 = componentName.toShortString()
+            },
+            { "Session destroyed. Active browser = $bool1. Browser component = $str1." }
+        )
+}
+
+private const val TAG = "MediaBrowser"
diff --git a/packages/SystemUI/src/com/android/systemui/media/AnimationBindHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/AnimationBindHandler.kt
similarity index 78%
rename from packages/SystemUI/src/com/android/systemui/media/AnimationBindHandler.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/AnimationBindHandler.kt
index 013683e..d2793bc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/AnimationBindHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/AnimationBindHandler.kt
@@ -14,19 +14,23 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
 
 import android.graphics.drawable.Animatable2
 import android.graphics.drawable.Drawable
 
 /**
- * AnimationBindHandler is responsible for tracking the bound animation state and preventing
- * jank and conflicts due to media notifications arriving at any time during an animation. It
- * does this in two parts.
- *  - Exit animations fired as a result of user input are tracked. When these are running, any
+ * AnimationBindHandler is responsible for tracking the bound animation state and preventing jank
+ * and conflicts due to media notifications arriving at any time during an animation. It does this
+ * in two parts.
+ * - Exit animations fired as a result of user input are tracked. When these are running, any
+ * ```
  *      bind actions are delayed until the animation completes (and then fired in sequence).
- *  - Continuous animations are tracked using their rebind id. Later calls using the same
+ * ```
+ * - Continuous animations are tracked using their rebind id. Later calls using the same
+ * ```
  *      rebind id will be totally ignored to prevent the continuous animation from restarting.
+ * ```
  */
 internal class AnimationBindHandler : Animatable2.AnimationCallback() {
     private val onAnimationsComplete = mutableListOf<() -> Unit>()
@@ -37,10 +41,10 @@
         get() = registrations.any { it.isRunning }
 
     /**
-     * This check prevents rebinding to the action button if the identifier has not changed. A
-     * null value is always considered to be changed. This is used to prevent the connecting
-     * animation from rebinding (and restarting) if multiple buffer PlaybackStates are pushed by
-     * an application in a row.
+     * This check prevents rebinding to the action button if the identifier has not changed. A null
+     * value is always considered to be changed. This is used to prevent the connecting animation
+     * from rebinding (and restarting) if multiple buffer PlaybackStates are pushed by an
+     * application in a row.
      */
     fun updateRebindId(newRebindId: Int?): Boolean {
         if (rebindId == null || newRebindId == null || rebindId != newRebindId) {
@@ -78,4 +82,4 @@
             onAnimationsComplete.clear()
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
new file mode 100644
index 0000000..61ef2f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
@@ -0,0 +1,223 @@
+/*
+ * 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.media.controls.ui
+
+import android.animation.ArgbEvaluator
+import android.animation.ValueAnimator
+import android.animation.ValueAnimator.AnimatorUpdateListener
+import android.content.Context
+import android.content.res.ColorStateList
+import android.content.res.Configuration
+import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import android.graphics.drawable.RippleDrawable
+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.monet.ColorScheme
+
+/**
+ * A [ColorTransition] is an object that updates the colors of views each time [updateColorScheme]
+ * is triggered.
+ */
+interface ColorTransition {
+    fun updateColorScheme(scheme: ColorScheme?): Boolean
+}
+
+/**
+ * A [ColorTransition] that animates between two specific colors. It uses a ValueAnimator to execute
+ * the animation and interpolate between the source color and the target color.
+ *
+ * Selection of the target color from the scheme, and application of the interpolated color are
+ * delegated to callbacks.
+ */
+open class AnimatingColorTransition(
+    private val defaultColor: Int,
+    private val extractColor: (ColorScheme) -> Int,
+    private val applyColor: (Int) -> Unit
+) : AnimatorUpdateListener, ColorTransition {
+
+    private val argbEvaluator = ArgbEvaluator()
+    private val valueAnimator = buildAnimator()
+    var sourceColor: Int = defaultColor
+    var currentColor: Int = defaultColor
+    var targetColor: Int = defaultColor
+
+    override fun onAnimationUpdate(animation: ValueAnimator) {
+        currentColor =
+            argbEvaluator.evaluate(animation.animatedFraction, sourceColor, targetColor) as Int
+        applyColor(currentColor)
+    }
+
+    override fun updateColorScheme(scheme: ColorScheme?): Boolean {
+        val newTargetColor = if (scheme == null) defaultColor else extractColor(scheme)
+        if (newTargetColor != targetColor) {
+            sourceColor = currentColor
+            targetColor = newTargetColor
+            valueAnimator.cancel()
+            valueAnimator.start()
+            return true
+        }
+        return false
+    }
+
+    init {
+        applyColor(defaultColor)
+    }
+
+    @VisibleForTesting
+    open fun buildAnimator(): ValueAnimator {
+        val animator = ValueAnimator.ofFloat(0f, 1f)
+        animator.duration = 333
+        animator.addUpdateListener(this)
+        return animator
+    }
+}
+
+typealias AnimatingColorTransitionFactory =
+    (Int, (ColorScheme) -> Int, (Int) -> Unit) -> AnimatingColorTransition
+
+/**
+ * ColorSchemeTransition constructs a ColorTransition for each color in the scheme that needs to be
+ * transitioned when changed. It also sets up the assignment functions for sending the sending the
+ * interpolated colors to the appropriate views.
+ */
+class ColorSchemeTransition
+internal constructor(
+    private val context: Context,
+    private val mediaViewHolder: MediaViewHolder,
+    animatingColorTransitionFactory: AnimatingColorTransitionFactory
+) {
+    constructor(
+        context: Context,
+        mediaViewHolder: MediaViewHolder
+    ) : this(context, mediaViewHolder, ::AnimatingColorTransition)
+
+    val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95)
+    val surfaceColor =
+        animatingColorTransitionFactory(bgColor, ::surfaceFromScheme) { surfaceColor ->
+            val colorList = ColorStateList.valueOf(surfaceColor)
+            mediaViewHolder.seamlessIcon.imageTintList = colorList
+            mediaViewHolder.seamlessText.setTextColor(surfaceColor)
+            mediaViewHolder.albumView.backgroundTintList = colorList
+            mediaViewHolder.gutsViewHolder.setSurfaceColor(surfaceColor)
+        }
+
+    val accentPrimary =
+        animatingColorTransitionFactory(
+            loadDefaultColor(R.attr.textColorPrimary),
+            ::accentPrimaryFromScheme
+        ) { accentPrimary ->
+            val accentColorList = ColorStateList.valueOf(accentPrimary)
+            mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList
+            mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary)
+        }
+
+    val accentSecondary =
+        animatingColorTransitionFactory(
+            loadDefaultColor(R.attr.textColorPrimary),
+            ::accentSecondaryFromScheme
+        ) { accentSecondary ->
+            val colorList = ColorStateList.valueOf(accentSecondary)
+            (mediaViewHolder.seamlessButton.background as? RippleDrawable)?.let {
+                it.setColor(colorList)
+                it.effectColor = colorList
+            }
+        }
+
+    val colorSeamless =
+        animatingColorTransitionFactory(
+            loadDefaultColor(R.attr.textColorPrimary),
+            { colorScheme: ColorScheme ->
+                // A1-100 dark in dark theme, A1-200 in light theme
+                if (
+                    context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
+                        UI_MODE_NIGHT_YES
+                )
+                    colorScheme.accent1[2]
+                else colorScheme.accent1[3]
+            },
+            { seamlessColor: Int ->
+                val accentColorList = ColorStateList.valueOf(seamlessColor)
+                mediaViewHolder.seamlessButton.backgroundTintList = accentColorList
+            }
+        )
+
+    val textPrimary =
+        animatingColorTransitionFactory(
+            loadDefaultColor(R.attr.textColorPrimary),
+            ::textPrimaryFromScheme
+        ) { textPrimary ->
+            mediaViewHolder.titleText.setTextColor(textPrimary)
+            val textColorList = ColorStateList.valueOf(textPrimary)
+            mediaViewHolder.seekBar.thumb.setTintList(textColorList)
+            mediaViewHolder.seekBar.progressTintList = textColorList
+            mediaViewHolder.scrubbingElapsedTimeView.setTextColor(textColorList)
+            mediaViewHolder.scrubbingTotalTimeView.setTextColor(textColorList)
+            for (button in mediaViewHolder.getTransparentActionButtons()) {
+                button.imageTintList = textColorList
+            }
+            mediaViewHolder.gutsViewHolder.setTextPrimaryColor(textPrimary)
+        }
+
+    val textPrimaryInverse =
+        animatingColorTransitionFactory(
+            loadDefaultColor(R.attr.textColorPrimaryInverse),
+            ::textPrimaryInverseFromScheme
+        ) { textPrimaryInverse ->
+            mediaViewHolder.actionPlayPause.imageTintList =
+                ColorStateList.valueOf(textPrimaryInverse)
+        }
+
+    val textSecondary =
+        animatingColorTransitionFactory(
+            loadDefaultColor(R.attr.textColorSecondary),
+            ::textSecondaryFromScheme
+        ) { textSecondary -> mediaViewHolder.artistText.setTextColor(textSecondary) }
+
+    val textTertiary =
+        animatingColorTransitionFactory(
+            loadDefaultColor(R.attr.textColorTertiary),
+            ::textTertiaryFromScheme
+        ) { textTertiary ->
+            mediaViewHolder.seekBar.progressBackgroundTintList =
+                ColorStateList.valueOf(textTertiary)
+        }
+
+    val colorTransitions =
+        arrayOf(
+            surfaceColor,
+            colorSeamless,
+            accentPrimary,
+            accentSecondary,
+            textPrimary,
+            textPrimaryInverse,
+            textSecondary,
+            textTertiary,
+        )
+
+    private fun loadDefaultColor(id: Int): Int {
+        return Utils.getColorAttr(context, id).defaultColor
+    }
+
+    fun updateColorScheme(colorScheme: ColorScheme?): Boolean {
+        var anyChanged = false
+        colorTransitions.forEach { anyChanged = it.updateColorScheme(colorScheme) || anyChanged }
+        colorScheme?.let { mediaViewHolder.gutsViewHolder.colorScheme = colorScheme }
+        return anyChanged
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
index 121ddd4..9f86cd8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
@@ -42,22 +42,20 @@
 
 private const val BACKGROUND_ANIM_DURATION = 370L
 
-/**
- * Drawable that can draw an animated gradient when tapped.
- */
+/** Drawable that can draw an animated gradient when tapped. */
 @Keep
 class IlluminationDrawable : Drawable() {
 
     private var themeAttrs: IntArray? = null
     private var cornerRadiusOverride = -1f
     var cornerRadius = 0f
-    get() {
-        return if (cornerRadiusOverride >= 0) {
-            cornerRadiusOverride
-        } else {
-            field
+        get() {
+            return if (cornerRadiusOverride >= 0) {
+                cornerRadiusOverride
+            } else {
+                field
+            }
         }
-    }
     private var highlightColor = Color.TRANSPARENT
     private var tmpHsl = floatArrayOf(0f, 0f, 0f)
     private var paint = Paint()
@@ -65,22 +63,27 @@
     private val lightSources = arrayListOf<LightSourceDrawable>()
 
     private var backgroundColor = Color.TRANSPARENT
-    set(value) {
-        if (value == field) {
-            return
+        set(value) {
+            if (value == field) {
+                return
+            }
+            field = value
+            animateBackground()
         }
-        field = value
-        animateBackground()
-    }
 
     private var backgroundAnimation: ValueAnimator? = null
 
-    /**
-     * Draw background and gradient.
-     */
+    /** Draw background and gradient. */
     override fun draw(canvas: Canvas) {
-        canvas.drawRoundRect(0f, 0f, bounds.width().toFloat(), bounds.height().toFloat(),
-                cornerRadius, cornerRadius, paint)
+        canvas.drawRoundRect(
+            0f,
+            0f,
+            bounds.width().toFloat(),
+            bounds.height().toFloat(),
+            cornerRadius,
+            cornerRadius,
+            paint
+        )
     }
 
     override fun getOutline(outline: Outline) {
@@ -105,12 +108,11 @@
 
     private fun updateStateFromTypedArray(a: TypedArray) {
         if (a.hasValue(R.styleable.IlluminationDrawable_cornerRadius)) {
-            cornerRadius = a.getDimension(R.styleable.IlluminationDrawable_cornerRadius,
-                    cornerRadius)
+            cornerRadius =
+                a.getDimension(R.styleable.IlluminationDrawable_cornerRadius, cornerRadius)
         }
         if (a.hasValue(R.styleable.IlluminationDrawable_highlight)) {
-            highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) /
-                    100f
+            highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) / 100f
         }
     }
 
@@ -163,34 +165,42 @@
     private fun animateBackground() {
         ColorUtils.colorToHSL(backgroundColor, tmpHsl)
         val L = tmpHsl[2]
-        tmpHsl[2] = MathUtils.constrain(if (L < 1f - highlight) {
-            L + highlight
-        } else {
-            L - highlight
-        }, 0f, 1f)
+        tmpHsl[2] =
+            MathUtils.constrain(
+                if (L < 1f - highlight) {
+                    L + highlight
+                } else {
+                    L - highlight
+                },
+                0f,
+                1f
+            )
 
         val initialBackground = paint.color
         val initialHighlight = highlightColor
         val finalHighlight = ColorUtils.HSLToColor(tmpHsl)
 
         backgroundAnimation?.cancel()
-        backgroundAnimation = ValueAnimator.ofFloat(0f, 1f).apply {
-            duration = BACKGROUND_ANIM_DURATION
-            interpolator = Interpolators.FAST_OUT_LINEAR_IN
-            addUpdateListener {
-                val progress = it.animatedValue as Float
-                paint.color = blendARGB(initialBackground, backgroundColor, progress)
-                highlightColor = blendARGB(initialHighlight, finalHighlight, progress)
-                lightSources.forEach { it.highlightColor = highlightColor }
-                invalidateSelf()
-            }
-            addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator?) {
-                    backgroundAnimation = null
+        backgroundAnimation =
+            ValueAnimator.ofFloat(0f, 1f).apply {
+                duration = BACKGROUND_ANIM_DURATION
+                interpolator = Interpolators.FAST_OUT_LINEAR_IN
+                addUpdateListener {
+                    val progress = it.animatedValue as Float
+                    paint.color = blendARGB(initialBackground, backgroundColor, progress)
+                    highlightColor = blendARGB(initialHighlight, finalHighlight, progress)
+                    lightSources.forEach { it.highlightColor = highlightColor }
+                    invalidateSelf()
                 }
-            })
-            start()
-        }
+                addListener(
+                    object : AnimatorListenerAdapter() {
+                        override fun onAnimationEnd(animation: Animator?) {
+                            backgroundAnimation = null
+                        }
+                    }
+                )
+                start()
+            }
     }
 
     override fun setTintList(tint: ColorStateList?) {
@@ -215,4 +225,4 @@
     fun setCornerRadiusOverride(cornerRadius: Float?) {
         cornerRadiusOverride = cornerRadius ?: -1f
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
similarity index 79%
rename from packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
index 32600fb..899148b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
 
 import android.content.Context
 import android.content.res.Configuration
@@ -45,7 +45,9 @@
  * switches media player positioning between split pane container vs single pane container
  */
 @SysUISingleton
-class KeyguardMediaController @Inject constructor(
+class KeyguardMediaController
+@Inject
+constructor(
     @param:Named(KEYGUARD) private val mediaHost: MediaHost,
     private val bypassController: KeyguardBypassController,
     private val statusBarStateController: SysuiStatusBarStateController,
@@ -56,34 +58,40 @@
 ) {
 
     init {
-        statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
-            override fun onStateChanged(newState: Int) {
-                refreshMediaPosition()
-            }
-        })
-        configurationController.addCallback(object : ConfigurationController.ConfigurationListener {
-            override fun onConfigChanged(newConfig: Configuration?) {
-                updateResources()
-            }
-        })
-
-        val settingsObserver: ContentObserver = object : ContentObserver(handler) {
-            override fun onChange(selfChange: Boolean, uri: Uri?) {
-                if (uri == lockScreenMediaPlayerUri) {
-                    allowMediaPlayerOnLockScreen =
-                            secureSettings.getBoolForUser(
-                                    Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
-                                    true,
-                                    UserHandle.USER_CURRENT
-                            )
+        statusBarStateController.addCallback(
+            object : StatusBarStateController.StateListener {
+                override fun onStateChanged(newState: Int) {
                     refreshMediaPosition()
                 }
             }
-        }
+        )
+        configurationController.addCallback(
+            object : ConfigurationController.ConfigurationListener {
+                override fun onConfigChanged(newConfig: Configuration?) {
+                    updateResources()
+                }
+            }
+        )
+
+        val settingsObserver: ContentObserver =
+            object : ContentObserver(handler) {
+                override fun onChange(selfChange: Boolean, uri: Uri?) {
+                    if (uri == lockScreenMediaPlayerUri) {
+                        allowMediaPlayerOnLockScreen =
+                            secureSettings.getBoolForUser(
+                                Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
+                                true,
+                                UserHandle.USER_CURRENT
+                            )
+                        refreshMediaPosition()
+                    }
+                }
+            }
         secureSettings.registerContentObserverForUser(
-                Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
-                settingsObserver,
-                UserHandle.USER_ALL)
+            Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
+            settingsObserver,
+            UserHandle.USER_ALL
+        )
 
         // First let's set the desired state that we want for this host
         mediaHost.expansion = MediaHostState.EXPANDED
@@ -110,27 +118,21 @@
             refreshMediaPosition()
         }
 
-    /**
-     * Is the media player visible?
-     */
+    /** Is the media player visible? */
     var visible = false
         private set
 
     var visibilityChangedListener: ((Boolean) -> Unit)? = null
 
-    /**
-     * single pane media container placed at the top of the notifications list
-     */
+    /** single pane media container placed at the top of the notifications list */
     var singlePaneContainer: MediaContainerView? = null
         private set
     private var splitShadeContainer: ViewGroup? = null
 
-    /**
-     * Track the media player setting status on lock screen.
-     */
+    /** Track the media player setting status on lock screen. */
     private var allowMediaPlayerOnLockScreen: Boolean = true
     private val lockScreenMediaPlayerUri =
-            secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN)
+        secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN)
 
     /**
      * Attaches media container in single pane mode, situated at the top of the notifications list
@@ -146,9 +148,7 @@
         onMediaHostVisibilityChanged(mediaHost.visible)
     }
 
-    /**
-     * Called whenever the media hosts visibility changes
-     */
+    /** Called whenever the media hosts visibility changes */
     private fun onMediaHostVisibilityChanged(visible: Boolean) {
         refreshMediaPosition()
         if (visible) {
@@ -159,9 +159,7 @@
         }
     }
 
-    /**
-     * Attaches media container in split shade mode, situated to the left of notifications
-     */
+    /** Attaches media container in split shade mode, situated to the left of notifications */
     fun attachSplitShadeContainer(container: ViewGroup) {
         splitShadeContainer = container
         reattachHostView()
@@ -183,9 +181,7 @@
         }
         if (activeContainer?.childCount == 0) {
             // Detach the hostView from its parent view if exists
-            mediaHost.hostView.parent?.let {
-                (it as? ViewGroup)?.removeView(mediaHost.hostView)
-            }
+            mediaHost.hostView.parent?.let { (it as? ViewGroup)?.removeView(mediaHost.hostView) }
             activeContainer.addView(mediaHost.hostView)
         }
     }
@@ -193,7 +189,8 @@
     fun refreshMediaPosition() {
         val keyguardOrUserSwitcher = (statusBarStateController.state == StatusBarState.KEYGUARD)
         // mediaHost.visible required for proper animations handling
-        visible = mediaHost.visible &&
+        visible =
+            mediaHost.visible &&
                 !bypassController.bypassEnabled &&
                 keyguardOrUserSwitcher &&
                 allowMediaPlayerOnLockScreen
diff --git a/packages/SystemUI/src/com/android/systemui/media/LightSourceDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
similarity index 64%
rename from packages/SystemUI/src/com/android/systemui/media/LightSourceDrawable.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
index 711cb36..dd5c2bf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/LightSourceDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
@@ -55,9 +55,7 @@
     var highlight: Float
 )
 
-/**
- * Drawable that can draw an animated gradient when tapped.
- */
+/** Drawable that can draw an animated gradient when tapped. */
 @Keep
 class LightSourceDrawable : Drawable() {
 
@@ -67,17 +65,15 @@
     private var paint = Paint()
 
     var highlightColor = Color.WHITE
-    set(value) {
-        if (field == value) {
-            return
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            invalidateSelf()
         }
-        field = value
-        invalidateSelf()
-    }
 
-    /**
-     * Draw a small highlight under the finger before expanding (or cancelling) it.
-     */
+    /** Draw a small highlight under the finger before expanding (or cancelling) it. */
     private var active: Boolean = false
         set(value) {
             if (value == field) {
@@ -91,46 +87,54 @@
                 rippleData.progress = RIPPLE_DOWN_PROGRESS
             } else {
                 rippleAnimation?.cancel()
-                rippleAnimation = ValueAnimator.ofFloat(rippleData.alpha, 0f).apply {
-                    duration = RIPPLE_CANCEL_DURATION
-                    interpolator = Interpolators.LINEAR_OUT_SLOW_IN
-                    addUpdateListener {
-                        rippleData.alpha = it.animatedValue as Float
-                        invalidateSelf()
-                    }
-                    addListener(object : AnimatorListenerAdapter() {
-                        var cancelled = false
-                        override fun onAnimationCancel(animation: Animator?) {
-                            cancelled = true
-                        }
-
-                        override fun onAnimationEnd(animation: Animator?) {
-                            if (cancelled) {
-                                return
-                            }
-                            rippleData.progress = 0f
-                            rippleData.alpha = 0f
-                            rippleAnimation = null
+                rippleAnimation =
+                    ValueAnimator.ofFloat(rippleData.alpha, 0f).apply {
+                        duration = RIPPLE_CANCEL_DURATION
+                        interpolator = Interpolators.LINEAR_OUT_SLOW_IN
+                        addUpdateListener {
+                            rippleData.alpha = it.animatedValue as Float
                             invalidateSelf()
                         }
-                    })
-                    start()
-                }
+                        addListener(
+                            object : AnimatorListenerAdapter() {
+                                var cancelled = false
+                                override fun onAnimationCancel(animation: Animator?) {
+                                    cancelled = true
+                                }
+
+                                override fun onAnimationEnd(animation: Animator?) {
+                                    if (cancelled) {
+                                        return
+                                    }
+                                    rippleData.progress = 0f
+                                    rippleData.alpha = 0f
+                                    rippleAnimation = null
+                                    invalidateSelf()
+                                }
+                            }
+                        )
+                        start()
+                    }
             }
             invalidateSelf()
         }
 
     private var rippleAnimation: Animator? = null
 
-    /**
-     * Draw background and gradient.
-     */
+    /** Draw background and gradient. */
     override fun draw(canvas: Canvas) {
         val radius = lerp(rippleData.minSize, rippleData.maxSize, rippleData.progress)
         val centerColor =
-                ColorUtils.setAlphaComponent(highlightColor, (rippleData.alpha * 255).toInt())
-        paint.shader = RadialGradient(rippleData.x, rippleData.y, radius,
-                intArrayOf(centerColor, Color.TRANSPARENT), GRADIENT_STOPS, Shader.TileMode.CLAMP)
+            ColorUtils.setAlphaComponent(highlightColor, (rippleData.alpha * 255).toInt())
+        paint.shader =
+            RadialGradient(
+                rippleData.x,
+                rippleData.y,
+                radius,
+                intArrayOf(centerColor, Color.TRANSPARENT),
+                GRADIENT_STOPS,
+                Shader.TileMode.CLAMP
+            )
         canvas.drawCircle(rippleData.x, rippleData.y, radius, paint)
     }
 
@@ -162,8 +166,8 @@
             rippleData.maxSize = a.getDimension(R.styleable.IlluminationDrawable_rippleMaxSize, 0f)
         }
         if (a.hasValue(R.styleable.IlluminationDrawable_highlight)) {
-            rippleData.highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) /
-                    100f
+            rippleData.highlight =
+                a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) / 100f
         }
     }
 
@@ -193,40 +197,44 @@
         invalidateSelf()
     }
 
-    /**
-     * Draws an animated ripple that expands fading away.
-     */
+    /** Draws an animated ripple that expands fading away. */
     private fun illuminate() {
         rippleData.alpha = 1f
         invalidateSelf()
 
         rippleAnimation?.cancel()
-        rippleAnimation = AnimatorSet().apply {
-            playTogether(ValueAnimator.ofFloat(1f, 0f).apply {
-                startDelay = 133
-                duration = RIPPLE_ANIM_DURATION - startDelay
-                interpolator = Interpolators.LINEAR_OUT_SLOW_IN
-                addUpdateListener {
-                    rippleData.alpha = it.animatedValue as Float
-                    invalidateSelf()
-                }
-            }, ValueAnimator.ofFloat(rippleData.progress, 1f).apply {
-                duration = RIPPLE_ANIM_DURATION
-                interpolator = Interpolators.LINEAR_OUT_SLOW_IN
-                addUpdateListener {
-                    rippleData.progress = it.animatedValue as Float
-                    invalidateSelf()
-                }
-            })
-            addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator?) {
-                    rippleData.progress = 0f
-                    rippleAnimation = null
-                    invalidateSelf()
-                }
-            })
-            start()
-        }
+        rippleAnimation =
+            AnimatorSet().apply {
+                playTogether(
+                    ValueAnimator.ofFloat(1f, 0f).apply {
+                        startDelay = 133
+                        duration = RIPPLE_ANIM_DURATION - startDelay
+                        interpolator = Interpolators.LINEAR_OUT_SLOW_IN
+                        addUpdateListener {
+                            rippleData.alpha = it.animatedValue as Float
+                            invalidateSelf()
+                        }
+                    },
+                    ValueAnimator.ofFloat(rippleData.progress, 1f).apply {
+                        duration = RIPPLE_ANIM_DURATION
+                        interpolator = Interpolators.LINEAR_OUT_SLOW_IN
+                        addUpdateListener {
+                            rippleData.progress = it.animatedValue as Float
+                            invalidateSelf()
+                        }
+                    }
+                )
+                addListener(
+                    object : AnimatorListenerAdapter() {
+                        override fun onAnimationEnd(animation: Animator?) {
+                            rippleData.progress = 0f
+                            rippleAnimation = null
+                            invalidateSelf()
+                        }
+                    }
+                )
+                start()
+            }
     }
 
     override fun setHotspot(x: Float, y: Float) {
@@ -251,8 +259,13 @@
 
     override fun getDirtyBounds(): Rect {
         val radius = lerp(rippleData.minSize, rippleData.maxSize, rippleData.progress)
-        val bounds = Rect((rippleData.x - radius).toInt(), (rippleData.y - radius).toInt(),
-                (rippleData.x + radius).toInt(), (rippleData.y + radius).toInt())
+        val bounds =
+            Rect(
+                (rippleData.x - radius).toInt(),
+                (rippleData.y - radius).toInt(),
+                (rippleData.x + radius).toInt(),
+                (rippleData.y + radius).toInt()
+            )
         bounds.union(super.getDirtyBounds())
         return bounds
     }
@@ -293,4 +306,4 @@
 
         return changed
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
new file mode 100644
index 0000000..e38c1ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -0,0 +1,1327 @@
+/*
+ * 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.media.controls.ui
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.res.ColorStateList
+import android.content.res.Configuration
+import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
+import android.util.Log
+import android.util.MathUtils
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.PathInterpolator
+import android.widget.LinearLayout
+import androidx.annotation.VisibleForTesting
+import com.android.internal.logging.InstanceId
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.classifier.FalsingCollector
+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.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.util.MediaUiEventLogger
+import com.android.systemui.media.controls.util.SmallHash
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.PageIndicator
+import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
+import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.Utils
+import com.android.systemui.util.animation.UniqueObjectHostView
+import com.android.systemui.util.animation.requiresRemeasuring
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
+import com.android.systemui.util.traceSection
+import java.io.PrintWriter
+import java.util.TreeMap
+import javax.inject.Inject
+import javax.inject.Provider
+
+private const val TAG = "MediaCarouselController"
+private val settingsIntent = Intent().setAction(ACTION_MEDIA_CONTROLS_SETTINGS)
+private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+
+/**
+ * Class that is responsible for keeping the view carousel up to date. This also handles changes in
+ * state and applies them to the media carousel like the expansion.
+ */
+@SysUISingleton
+class MediaCarouselController
+@Inject
+constructor(
+    private val context: Context,
+    private val mediaControlPanelFactory: Provider<MediaControlPanel>,
+    private val visualStabilityProvider: VisualStabilityProvider,
+    private val mediaHostStatesManager: MediaHostStatesManager,
+    private val activityStarter: ActivityStarter,
+    private val systemClock: SystemClock,
+    @Main executor: DelayableExecutor,
+    private val mediaManager: MediaDataManager,
+    configurationController: ConfigurationController,
+    falsingCollector: FalsingCollector,
+    falsingManager: FalsingManager,
+    dumpManager: DumpManager,
+    private val logger: MediaUiEventLogger,
+    private val debugLogger: MediaCarouselControllerLogger
+) : Dumpable {
+    /** The current width of the carousel */
+    private var currentCarouselWidth: Int = 0
+
+    /** The current height of the carousel */
+    private var currentCarouselHeight: Int = 0
+
+    /** Are we currently showing only active players */
+    private var currentlyShowingOnlyActive: Boolean = false
+
+    /** Is the player currently visible (at the end of the transformation */
+    private var playersVisible: Boolean = false
+    /**
+     * The desired location where we'll be at the end of the transformation. Usually this matches
+     * the end location, except when we're still waiting on a state update call.
+     */
+    @MediaLocation private var desiredLocation: Int = -1
+
+    /**
+     * The ending location of the view where it ends when all animations and transitions have
+     * finished
+     */
+    @MediaLocation @VisibleForTesting var currentEndLocation: Int = -1
+
+    /**
+     * The ending location of the view where it ends when all animations and transitions have
+     * finished
+     */
+    @MediaLocation private var currentStartLocation: Int = -1
+
+    /** The progress of the transition or 1.0 if there is no transition happening */
+    private var currentTransitionProgress: Float = 1.0f
+
+    /** The measured width of the carousel */
+    private var carouselMeasureWidth: Int = 0
+
+    /** The measured height of the carousel */
+    private var carouselMeasureHeight: Int = 0
+    private var desiredHostState: MediaHostState? = null
+    private val mediaCarousel: MediaScrollView
+    val mediaCarouselScrollHandler: MediaCarouselScrollHandler
+    val mediaFrame: ViewGroup
+    @VisibleForTesting
+    lateinit var settingsButton: View
+        private set
+    private val mediaContent: ViewGroup
+    @VisibleForTesting val pageIndicator: PageIndicator
+    private val visualStabilityCallback: OnReorderingAllowedListener
+    private var needsReordering: Boolean = false
+    private var keysNeedRemoval = mutableSetOf<String>()
+    var shouldScrollToKey: Boolean = false
+    private var isRtl: Boolean = false
+        set(value) {
+            if (value != field) {
+                field = value
+                mediaFrame.layoutDirection =
+                    if (value) View.LAYOUT_DIRECTION_RTL else View.LAYOUT_DIRECTION_LTR
+                mediaCarouselScrollHandler.scrollToStart()
+            }
+        }
+    private var currentlyExpanded = true
+        set(value) {
+            if (field != value) {
+                field = value
+                for (player in MediaPlayerData.players()) {
+                    player.setListening(field)
+                }
+            }
+        }
+
+    companion object {
+        const val ANIMATION_BASE_DURATION = 2200f
+        const val DURATION = 167f
+        const val DETAILS_DELAY = 1067f
+        const val CONTROLS_DELAY = 1400f
+        const val PAGINATION_DELAY = 1900f
+        const val MEDIATITLES_DELAY = 1000f
+        const val MEDIACONTAINERS_DELAY = 967f
+        val TRANSFORM_BEZIER = PathInterpolator(0.68F, 0F, 0F, 1F)
+        val REVERSE_BEZIER = PathInterpolator(0F, 0.68F, 1F, 0F)
+
+        fun calculateAlpha(squishinessFraction: Float, delay: Float, duration: Float): Float {
+            val transformStartFraction = delay / ANIMATION_BASE_DURATION
+            val transformDurationFraction = duration / ANIMATION_BASE_DURATION
+            val squishinessToTime = REVERSE_BEZIER.getInterpolation(squishinessFraction)
+            return MathUtils.constrain(
+                (squishinessToTime - transformStartFraction) / transformDurationFraction,
+                0F,
+                1F
+            )
+        }
+    }
+
+    private val configListener =
+        object : ConfigurationController.ConfigurationListener {
+            override fun onDensityOrFontScaleChanged() {
+                // System font changes should only happen when UMO is offscreen or a flicker may
+                // occur
+                updatePlayers(recreateMedia = true)
+                inflateSettingsButton()
+            }
+
+            override fun onThemeChanged() {
+                updatePlayers(recreateMedia = false)
+                inflateSettingsButton()
+            }
+
+            override fun onConfigChanged(newConfig: Configuration?) {
+                if (newConfig == null) return
+                isRtl = newConfig.layoutDirection == View.LAYOUT_DIRECTION_RTL
+            }
+
+            override fun onUiModeChanged() {
+                updatePlayers(recreateMedia = false)
+                inflateSettingsButton()
+            }
+        }
+
+    /**
+     * Update MediaCarouselScrollHandler.visibleToUser to reflect media card container visibility.
+     * It will be called when the container is out of view.
+     */
+    lateinit var updateUserVisibility: () -> Unit
+    lateinit var updateHostVisibility: () -> Unit
+
+    private val isReorderingAllowed: Boolean
+        get() = visualStabilityProvider.isReorderingAllowed
+
+    init {
+        dumpManager.registerDumpable(TAG, this)
+        mediaFrame = inflateMediaCarousel()
+        mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller)
+        pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
+        mediaCarouselScrollHandler =
+            MediaCarouselScrollHandler(
+                mediaCarousel,
+                pageIndicator,
+                executor,
+                this::onSwipeToDismiss,
+                this::updatePageIndicatorLocation,
+                this::closeGuts,
+                falsingCollector,
+                falsingManager,
+                this::logSmartspaceImpression,
+                logger
+            )
+        isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
+        inflateSettingsButton()
+        mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
+        configurationController.addCallback(configListener)
+        visualStabilityCallback = OnReorderingAllowedListener {
+            if (needsReordering) {
+                needsReordering = false
+                reorderAllPlayers(previousVisiblePlayerKey = null)
+            }
+
+            keysNeedRemoval.forEach { removePlayer(it) }
+            if (keysNeedRemoval.size > 0) {
+                // Carousel visibility may need to be updated after late removals
+                updateHostVisibility()
+            }
+            keysNeedRemoval.clear()
+
+            // Update user visibility so that no extra impression will be logged when
+            // activeMediaIndex resets to 0
+            if (this::updateUserVisibility.isInitialized) {
+                updateUserVisibility()
+            }
+
+            // Let's reset our scroll position
+            mediaCarouselScrollHandler.scrollToStart()
+        }
+        visualStabilityProvider.addPersistentReorderingAllowedListener(visualStabilityCallback)
+        mediaManager.addListener(
+            object : MediaDataManager.Listener {
+                override fun onMediaDataLoaded(
+                    key: String,
+                    oldKey: String?,
+                    data: MediaData,
+                    immediately: Boolean,
+                    receivedSmartspaceCardLatency: Int,
+                    isSsReactivated: Boolean
+                ) {
+                    debugLogger.logMediaLoaded(key)
+                    if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated)) {
+                        // Log card received if a new resumable media card is added
+                        MediaPlayerData.getMediaPlayer(key)?.let {
+                            /* ktlint-disable max-line-length */
+                            logSmartspaceCardReported(
+                                759, // SMARTSPACE_CARD_RECEIVED
+                                it.mSmartspaceId,
+                                it.mUid,
+                                surfaces =
+                                    intArrayOf(
+                                        SysUiStatsLog
+                                            .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
+                                        SysUiStatsLog
+                                            .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
+                                        SysUiStatsLog
+                                            .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY
+                                    ),
+                                rank = MediaPlayerData.getMediaPlayerIndex(key)
+                            )
+                            /* ktlint-disable max-line-length */
+                        }
+                        if (
+                            mediaCarouselScrollHandler.visibleToUser &&
+                                mediaCarouselScrollHandler.visibleMediaIndex ==
+                                    MediaPlayerData.getMediaPlayerIndex(key)
+                        ) {
+                            logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded)
+                        }
+                    } else if (receivedSmartspaceCardLatency != 0) {
+                        // Log resume card received if resumable media card is reactivated and
+                        // resume card is ranked first
+                        MediaPlayerData.players().forEachIndexed { index, it ->
+                            if (it.recommendationViewHolder == null) {
+                                it.mSmartspaceId =
+                                    SmallHash.hash(
+                                        it.mUid + systemClock.currentTimeMillis().toInt()
+                                    )
+                                it.mIsImpressed = false
+                                /* ktlint-disable max-line-length */
+                                logSmartspaceCardReported(
+                                    759, // SMARTSPACE_CARD_RECEIVED
+                                    it.mSmartspaceId,
+                                    it.mUid,
+                                    surfaces =
+                                        intArrayOf(
+                                            SysUiStatsLog
+                                                .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
+                                            SysUiStatsLog
+                                                .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
+                                            SysUiStatsLog
+                                                .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY
+                                        ),
+                                    rank = index,
+                                    receivedLatencyMillis = receivedSmartspaceCardLatency
+                                )
+                                /* ktlint-disable max-line-length */
+                            }
+                        }
+                        // If media container area already visible to the user, log impression for
+                        // reactivated card.
+                        if (
+                            mediaCarouselScrollHandler.visibleToUser &&
+                                !mediaCarouselScrollHandler.qsExpanded
+                        ) {
+                            logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded)
+                        }
+                    }
+
+                    val canRemove = data.isPlaying?.let { !it } ?: data.isClearable && !data.active
+                    if (canRemove && !Utils.useMediaResumption(context)) {
+                        // This view isn't playing, let's remove this! This happens e.g. when
+                        // dismissing/timing out a view. We still have the data around because
+                        // resumption could be on, but we should save the resources and release
+                        // this.
+                        if (isReorderingAllowed) {
+                            onMediaDataRemoved(key)
+                        } else {
+                            keysNeedRemoval.add(key)
+                        }
+                    } else {
+                        keysNeedRemoval.remove(key)
+                    }
+                }
+
+                override fun onSmartspaceMediaDataLoaded(
+                    key: String,
+                    data: SmartspaceMediaData,
+                    shouldPrioritize: Boolean
+                ) {
+                    debugLogger.logRecommendationLoaded(key)
+                    // Log the case where the hidden media carousel with the existed inactive resume
+                    // media is shown by the Smartspace signal.
+                    if (data.isActive) {
+                        val hasActivatedExistedResumeMedia =
+                            !mediaManager.hasActiveMedia() &&
+                                mediaManager.hasAnyMedia() &&
+                                shouldPrioritize
+                        if (hasActivatedExistedResumeMedia) {
+                            // Log resume card received if resumable media card is reactivated and
+                            // recommendation card is valid and ranked first
+                            MediaPlayerData.players().forEachIndexed { index, it ->
+                                if (it.recommendationViewHolder == null) {
+                                    it.mSmartspaceId =
+                                        SmallHash.hash(
+                                            it.mUid + systemClock.currentTimeMillis().toInt()
+                                        )
+                                    it.mIsImpressed = false
+                                    /* ktlint-disable max-line-length */
+                                    logSmartspaceCardReported(
+                                        759, // SMARTSPACE_CARD_RECEIVED
+                                        it.mSmartspaceId,
+                                        it.mUid,
+                                        surfaces =
+                                            intArrayOf(
+                                                SysUiStatsLog
+                                                    .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
+                                                SysUiStatsLog
+                                                    .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
+                                                SysUiStatsLog
+                                                    .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY
+                                            ),
+                                        rank = index,
+                                        receivedLatencyMillis =
+                                            (systemClock.currentTimeMillis() -
+                                                    data.headphoneConnectionTimeMillis)
+                                                .toInt()
+                                    )
+                                    /* ktlint-disable max-line-length */
+                                }
+                            }
+                        }
+                        addSmartspaceMediaRecommendations(key, data, shouldPrioritize)
+                        MediaPlayerData.getMediaPlayer(key)?.let {
+                            /* ktlint-disable max-line-length */
+                            logSmartspaceCardReported(
+                                759, // SMARTSPACE_CARD_RECEIVED
+                                it.mSmartspaceId,
+                                it.mUid,
+                                surfaces =
+                                    intArrayOf(
+                                        SysUiStatsLog
+                                            .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
+                                        SysUiStatsLog
+                                            .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
+                                        SysUiStatsLog
+                                            .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY
+                                    ),
+                                rank = MediaPlayerData.getMediaPlayerIndex(key),
+                                receivedLatencyMillis =
+                                    (systemClock.currentTimeMillis() -
+                                            data.headphoneConnectionTimeMillis)
+                                        .toInt()
+                            )
+                            /* ktlint-disable max-line-length */
+                        }
+                        if (
+                            mediaCarouselScrollHandler.visibleToUser &&
+                                mediaCarouselScrollHandler.visibleMediaIndex ==
+                                    MediaPlayerData.getMediaPlayerIndex(key)
+                        ) {
+                            logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded)
+                        }
+                    } else {
+                        onSmartspaceMediaDataRemoved(data.targetId, immediately = true)
+                    }
+                }
+
+                override fun onMediaDataRemoved(key: String) {
+                    debugLogger.logMediaRemoved(key)
+                    removePlayer(key)
+                }
+
+                override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
+                    debugLogger.logRecommendationRemoved(key, immediately)
+                    if (immediately || isReorderingAllowed) {
+                        removePlayer(key)
+                        if (!immediately) {
+                            // Although it wasn't requested, we were able to process the removal
+                            // immediately since reordering is allowed. So, notify hosts to update
+                            if (this@MediaCarouselController::updateHostVisibility.isInitialized) {
+                                updateHostVisibility()
+                            }
+                        }
+                    } else {
+                        keysNeedRemoval.add(key)
+                    }
+                }
+            }
+        )
+        mediaFrame.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
+            // The pageIndicator is not laid out yet when we get the current state update,
+            // Lets make sure we have the right dimensions
+            updatePageIndicatorLocation()
+        }
+        mediaHostStatesManager.addCallback(
+            object : MediaHostStatesManager.Callback {
+                override fun onHostStateChanged(location: Int, mediaHostState: MediaHostState) {
+                    if (location == desiredLocation) {
+                        onDesiredLocationChanged(desiredLocation, mediaHostState, animate = false)
+                    }
+                }
+            }
+        )
+    }
+
+    private fun inflateSettingsButton() {
+        val settings =
+            LayoutInflater.from(context)
+                .inflate(R.layout.media_carousel_settings_button, mediaFrame, false) as View
+        if (this::settingsButton.isInitialized) {
+            mediaFrame.removeView(settingsButton)
+        }
+        settingsButton = settings
+        mediaFrame.addView(settingsButton)
+        mediaCarouselScrollHandler.onSettingsButtonUpdated(settings)
+        settingsButton.setOnClickListener {
+            logger.logCarouselSettings()
+            activityStarter.startActivity(settingsIntent, true /* dismissShade */)
+        }
+    }
+
+    private fun inflateMediaCarousel(): ViewGroup {
+        val mediaCarousel =
+            LayoutInflater.from(context)
+                .inflate(R.layout.media_carousel, UniqueObjectHostView(context), false) as ViewGroup
+        // Because this is inflated when not attached to the true view hierarchy, it resolves some
+        // potential issues to force that the layout direction is defined by the locale
+        // (rather than inherited from the parent, which would resolve to LTR when unattached).
+        mediaCarousel.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
+        return mediaCarousel
+    }
+
+    private fun reorderAllPlayers(
+        previousVisiblePlayerKey: MediaPlayerData.MediaSortKey?,
+        key: String? = null
+    ) {
+        mediaContent.removeAllViews()
+        for (mediaPlayer in MediaPlayerData.players()) {
+            mediaPlayer.mediaViewHolder?.let { mediaContent.addView(it.player) }
+                ?: mediaPlayer.recommendationViewHolder?.let {
+                    mediaContent.addView(it.recommendations)
+                }
+        }
+        mediaCarouselScrollHandler.onPlayersChanged()
+        MediaPlayerData.updateVisibleMediaPlayers()
+        // Automatically scroll to the active player if needed
+        if (shouldScrollToKey) {
+            shouldScrollToKey = false
+            val mediaIndex = key?.let { MediaPlayerData.getMediaPlayerIndex(it) } ?: -1
+            if (mediaIndex != -1) {
+                previousVisiblePlayerKey?.let {
+                    val previousVisibleIndex =
+                        MediaPlayerData.playerKeys().indexOfFirst { key -> it == key }
+                    mediaCarouselScrollHandler.scrollToPlayer(previousVisibleIndex, mediaIndex)
+                }
+                    ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = mediaIndex)
+            }
+        }
+    }
+
+    // Returns true if new player is added
+    private fun addOrUpdatePlayer(
+        key: String,
+        oldKey: String?,
+        data: MediaData,
+        isSsReactivated: Boolean
+    ): Boolean =
+        traceSection("MediaCarouselController#addOrUpdatePlayer") {
+            MediaPlayerData.moveIfExists(oldKey, key)
+            val existingPlayer = MediaPlayerData.getMediaPlayer(key)
+            val curVisibleMediaKey =
+                MediaPlayerData.visiblePlayerKeys()
+                    .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
+            if (existingPlayer == null) {
+                val newPlayer = mediaControlPanelFactory.get()
+                newPlayer.attachPlayer(
+                    MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
+                )
+                newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
+                val lp =
+                    LinearLayout.LayoutParams(
+                        ViewGroup.LayoutParams.MATCH_PARENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT
+                    )
+                newPlayer.mediaViewHolder?.player?.setLayoutParams(lp)
+                newPlayer.bindPlayer(data, key)
+                newPlayer.setListening(currentlyExpanded)
+                MediaPlayerData.addMediaPlayer(
+                    key,
+                    data,
+                    newPlayer,
+                    systemClock,
+                    isSsReactivated,
+                    debugLogger
+                )
+                updatePlayerToState(newPlayer, noAnimation = true)
+                // Media data added from a recommendation card should starts playing.
+                if (
+                    (shouldScrollToKey && data.isPlaying == true) ||
+                        (!shouldScrollToKey && data.active)
+                ) {
+                    reorderAllPlayers(curVisibleMediaKey, key)
+                } else {
+                    needsReordering = true
+                }
+            } else {
+                existingPlayer.bindPlayer(data, key)
+                MediaPlayerData.addMediaPlayer(
+                    key,
+                    data,
+                    existingPlayer,
+                    systemClock,
+                    isSsReactivated,
+                    debugLogger
+                )
+                val packageName = MediaPlayerData.smartspaceMediaData?.packageName ?: String()
+                // In case of recommendations hits.
+                // Check the playing status of media player and the package name.
+                // To make sure we scroll to the right app's media player.
+                if (
+                    isReorderingAllowed ||
+                        shouldScrollToKey &&
+                            data.isPlaying == true &&
+                            packageName == data.packageName
+                ) {
+                    reorderAllPlayers(curVisibleMediaKey, key)
+                } else {
+                    needsReordering = true
+                }
+            }
+            updatePageIndicator()
+            mediaCarouselScrollHandler.onPlayersChanged()
+            mediaFrame.requiresRemeasuring = true
+            // Check postcondition: mediaContent should have the same number of children as there
+            // are
+            // elements in mediaPlayers.
+            if (MediaPlayerData.players().size != mediaContent.childCount) {
+                Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync")
+            }
+            return existingPlayer == null
+        }
+
+    private fun addSmartspaceMediaRecommendations(
+        key: String,
+        data: SmartspaceMediaData,
+        shouldPrioritize: Boolean
+    ) =
+        traceSection("MediaCarouselController#addSmartspaceMediaRecommendations") {
+            if (DEBUG) Log.d(TAG, "Updating smartspace target in carousel")
+            if (MediaPlayerData.getMediaPlayer(key) != null) {
+                Log.w(TAG, "Skip adding smartspace target in carousel")
+                return
+            }
+
+            val existingSmartspaceMediaKey = MediaPlayerData.smartspaceMediaKey()
+            existingSmartspaceMediaKey?.let {
+                val removedPlayer =
+                    MediaPlayerData.removeMediaPlayer(existingSmartspaceMediaKey, true)
+                removedPlayer?.run {
+                    debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey)
+                }
+            }
+
+            val newRecs = mediaControlPanelFactory.get()
+            newRecs.attachRecommendation(
+                RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent)
+            )
+            newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
+            val lp =
+                LinearLayout.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT
+                )
+            newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp)
+            newRecs.bindRecommendation(data)
+            val curVisibleMediaKey =
+                MediaPlayerData.visiblePlayerKeys()
+                    .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
+            MediaPlayerData.addMediaRecommendation(
+                key,
+                data,
+                newRecs,
+                shouldPrioritize,
+                systemClock,
+                debugLogger
+            )
+            updatePlayerToState(newRecs, noAnimation = true)
+            reorderAllPlayers(curVisibleMediaKey)
+            updatePageIndicator()
+            mediaFrame.requiresRemeasuring = true
+            // Check postcondition: mediaContent should have the same number of children as there
+            // are
+            // elements in mediaPlayers.
+            if (MediaPlayerData.players().size != mediaContent.childCount) {
+                Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync")
+            }
+        }
+
+    fun removePlayer(
+        key: String,
+        dismissMediaData: Boolean = true,
+        dismissRecommendation: Boolean = true
+    ) {
+        if (key == MediaPlayerData.smartspaceMediaKey()) {
+            MediaPlayerData.smartspaceMediaData?.let {
+                logger.logRecommendationRemoved(it.packageName, it.instanceId)
+            }
+        }
+        val removed =
+            MediaPlayerData.removeMediaPlayer(key, dismissMediaData || dismissRecommendation)
+        removed?.apply {
+            mediaCarouselScrollHandler.onPrePlayerRemoved(removed)
+            mediaContent.removeView(removed.mediaViewHolder?.player)
+            mediaContent.removeView(removed.recommendationViewHolder?.recommendations)
+            removed.onDestroy()
+            mediaCarouselScrollHandler.onPlayersChanged()
+            updatePageIndicator()
+
+            if (dismissMediaData) {
+                // Inform the media manager of a potentially late dismissal
+                mediaManager.dismissMediaData(key, delay = 0L)
+            }
+            if (dismissRecommendation) {
+                // Inform the media manager of a potentially late dismissal
+                mediaManager.dismissSmartspaceRecommendation(key, delay = 0L)
+            }
+        }
+    }
+
+    private fun updatePlayers(recreateMedia: Boolean) {
+        pageIndicator.tintList =
+            ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
+
+        MediaPlayerData.mediaData().forEach { (key, data, isSsMediaRec) ->
+            if (isSsMediaRec) {
+                val smartspaceMediaData = MediaPlayerData.smartspaceMediaData
+                removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
+                smartspaceMediaData?.let {
+                    addSmartspaceMediaRecommendations(
+                        it.targetId,
+                        it,
+                        MediaPlayerData.shouldPrioritizeSs
+                    )
+                }
+            } else {
+                val isSsReactivated = MediaPlayerData.isSsReactivated(key)
+                if (recreateMedia) {
+                    removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
+                }
+                addOrUpdatePlayer(
+                    key = key,
+                    oldKey = null,
+                    data = data,
+                    isSsReactivated = isSsReactivated
+                )
+            }
+        }
+    }
+
+    private fun updatePageIndicator() {
+        val numPages = mediaContent.getChildCount()
+        pageIndicator.setNumPages(numPages)
+        if (numPages == 1) {
+            pageIndicator.setLocation(0f)
+        }
+        updatePageIndicatorAlpha()
+    }
+
+    /**
+     * Set a new interpolated state for all players. This is a state that is usually controlled by a
+     * finger movement where the user drags from one state to the next.
+     *
+     * @param startLocation the start location of our state or -1 if this is directly set
+     * @param endLocation the ending location of our state.
+     * @param progress the progress of the transition between startLocation and endlocation. If
+     * ```
+     *                 this is not a guided transformation, this will be 1.0f
+     * @param immediately
+     * ```
+     * should this state be applied immediately, canceling all animations?
+     */
+    fun setCurrentState(
+        @MediaLocation startLocation: Int,
+        @MediaLocation endLocation: Int,
+        progress: Float,
+        immediately: Boolean
+    ) {
+        if (
+            startLocation != currentStartLocation ||
+                endLocation != currentEndLocation ||
+                progress != currentTransitionProgress ||
+                immediately
+        ) {
+            currentStartLocation = startLocation
+            currentEndLocation = endLocation
+            currentTransitionProgress = progress
+            for (mediaPlayer in MediaPlayerData.players()) {
+                updatePlayerToState(mediaPlayer, immediately)
+            }
+            maybeResetSettingsCog()
+            updatePageIndicatorAlpha()
+        }
+    }
+
+    @VisibleForTesting
+    fun updatePageIndicatorAlpha() {
+        val hostStates = mediaHostStatesManager.mediaHostStates
+        val endIsVisible = hostStates[currentEndLocation]?.visible ?: false
+        val startIsVisible = hostStates[currentStartLocation]?.visible ?: false
+        val startAlpha = if (startIsVisible) 1.0f else 0.0f
+        // when squishing in split shade, only use endState, which keeps changing
+        // to provide squishFraction
+        val squishFraction = hostStates[currentEndLocation]?.squishFraction ?: 1.0F
+        val endAlpha =
+            (if (endIsVisible) 1.0f else 0.0f) *
+                calculateAlpha(squishFraction, PAGINATION_DELAY, DURATION)
+        var alpha = 1.0f
+        if (!endIsVisible || !startIsVisible) {
+            var progress = currentTransitionProgress
+            if (!endIsVisible) {
+                progress = 1.0f - progress
+            }
+            // Let's fade in quickly at the end where the view is visible
+            progress =
+                MathUtils.constrain(MathUtils.map(0.95f, 1.0f, 0.0f, 1.0f, progress), 0.0f, 1.0f)
+            alpha = MathUtils.lerp(startAlpha, endAlpha, progress)
+        }
+        pageIndicator.alpha = alpha
+    }
+
+    private fun updatePageIndicatorLocation() {
+        // Update the location of the page indicator, carousel clipping
+        val translationX =
+            if (isRtl) {
+                (pageIndicator.width - currentCarouselWidth) / 2.0f
+            } else {
+                (currentCarouselWidth - pageIndicator.width) / 2.0f
+            }
+        pageIndicator.translationX = translationX + mediaCarouselScrollHandler.contentTranslation
+        val layoutParams = pageIndicator.layoutParams as ViewGroup.MarginLayoutParams
+        pageIndicator.translationY =
+            (currentCarouselHeight - pageIndicator.height - layoutParams.bottomMargin).toFloat()
+    }
+
+    /** Update the dimension of this carousel. */
+    private fun updateCarouselDimensions() {
+        var width = 0
+        var height = 0
+        for (mediaPlayer in MediaPlayerData.players()) {
+            val controller = mediaPlayer.mediaViewController
+            // When transitioning the view to gone, the view gets smaller, but the translation
+            // Doesn't, let's add the translation
+            width = Math.max(width, controller.currentWidth + controller.translationX.toInt())
+            height = Math.max(height, controller.currentHeight + controller.translationY.toInt())
+        }
+        if (width != currentCarouselWidth || height != currentCarouselHeight) {
+            currentCarouselWidth = width
+            currentCarouselHeight = height
+            mediaCarouselScrollHandler.setCarouselBounds(
+                currentCarouselWidth,
+                currentCarouselHeight
+            )
+            updatePageIndicatorLocation()
+            updatePageIndicatorAlpha()
+        }
+    }
+
+    private fun maybeResetSettingsCog() {
+        val hostStates = mediaHostStatesManager.mediaHostStates
+        val endShowsActive = hostStates[currentEndLocation]?.showsOnlyActiveMedia ?: true
+        val startShowsActive =
+            hostStates[currentStartLocation]?.showsOnlyActiveMedia ?: endShowsActive
+        if (
+            currentlyShowingOnlyActive != endShowsActive ||
+                ((currentTransitionProgress != 1.0f && currentTransitionProgress != 0.0f) &&
+                    startShowsActive != endShowsActive)
+        ) {
+            // Whenever we're transitioning from between differing states or the endstate differs
+            // we reset the translation
+            currentlyShowingOnlyActive = endShowsActive
+            mediaCarouselScrollHandler.resetTranslation(animate = true)
+        }
+    }
+
+    private fun updatePlayerToState(mediaPlayer: MediaControlPanel, noAnimation: Boolean) {
+        mediaPlayer.mediaViewController.setCurrentState(
+            startLocation = currentStartLocation,
+            endLocation = currentEndLocation,
+            transitionProgress = currentTransitionProgress,
+            applyImmediately = noAnimation
+        )
+    }
+
+    /**
+     * The desired location of this view has changed. We should remeasure the view to match the new
+     * bounds and kick off bounds animations if necessary. If an animation is happening, an
+     * animation is kicked of externally, which sets a new current state until we reach the
+     * targetState.
+     *
+     * @param desiredLocation the location we're going to
+     * @param desiredHostState the target state we're transitioning to
+     * @param animate should this be animated
+     */
+    fun onDesiredLocationChanged(
+        desiredLocation: Int,
+        desiredHostState: MediaHostState?,
+        animate: Boolean,
+        duration: Long = 200,
+        startDelay: Long = 0
+    ) =
+        traceSection("MediaCarouselController#onDesiredLocationChanged") {
+            desiredHostState?.let {
+                if (this.desiredLocation != desiredLocation) {
+                    // Only log an event when location changes
+                    logger.logCarouselPosition(desiredLocation)
+                }
+
+                // This is a hosting view, let's remeasure our players
+                this.desiredLocation = desiredLocation
+                this.desiredHostState = it
+                currentlyExpanded = it.expansion > 0
+
+                val shouldCloseGuts =
+                    !currentlyExpanded &&
+                        !mediaManager.hasActiveMediaOrRecommendation() &&
+                        desiredHostState.showsOnlyActiveMedia
+
+                for (mediaPlayer in MediaPlayerData.players()) {
+                    if (animate) {
+                        mediaPlayer.mediaViewController.animatePendingStateChange(
+                            duration = duration,
+                            delay = startDelay
+                        )
+                    }
+                    if (shouldCloseGuts && mediaPlayer.mediaViewController.isGutsVisible) {
+                        mediaPlayer.closeGuts(!animate)
+                    }
+
+                    mediaPlayer.mediaViewController.onLocationPreChange(desiredLocation)
+                }
+                mediaCarouselScrollHandler.showsSettingsButton = !it.showsOnlyActiveMedia
+                mediaCarouselScrollHandler.falsingProtectionNeeded = it.falsingProtectionNeeded
+                val nowVisible = it.visible
+                if (nowVisible != playersVisible) {
+                    playersVisible = nowVisible
+                    if (nowVisible) {
+                        mediaCarouselScrollHandler.resetTranslation()
+                    }
+                }
+                updateCarouselSize()
+            }
+        }
+
+    fun closeGuts(immediate: Boolean = true) {
+        MediaPlayerData.players().forEach { it.closeGuts(immediate) }
+    }
+
+    /** Update the size of the carousel, remeasuring it if necessary. */
+    private fun updateCarouselSize() {
+        val width = desiredHostState?.measurementInput?.width ?: 0
+        val height = desiredHostState?.measurementInput?.height ?: 0
+        if (
+            width != carouselMeasureWidth && width != 0 ||
+                height != carouselMeasureHeight && height != 0
+        ) {
+            carouselMeasureWidth = width
+            carouselMeasureHeight = height
+            val playerWidthPlusPadding =
+                carouselMeasureWidth +
+                    context.resources.getDimensionPixelSize(R.dimen.qs_media_padding)
+            // Let's remeasure the carousel
+            val widthSpec = desiredHostState?.measurementInput?.widthMeasureSpec ?: 0
+            val heightSpec = desiredHostState?.measurementInput?.heightMeasureSpec ?: 0
+            mediaCarousel.measure(widthSpec, heightSpec)
+            mediaCarousel.layout(0, 0, width, mediaCarousel.measuredHeight)
+            // Update the padding after layout; view widths are used in RTL to calculate scrollX
+            mediaCarouselScrollHandler.playerWidthPlusPadding = playerWidthPlusPadding
+        }
+    }
+
+    /** Log the user impression for media card at visibleMediaIndex. */
+    fun logSmartspaceImpression(qsExpanded: Boolean) {
+        val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex
+        if (MediaPlayerData.players().size > visibleMediaIndex) {
+            val mediaControlPanel = MediaPlayerData.getMediaControlPanel(visibleMediaIndex)
+            val hasActiveMediaOrRecommendationCard =
+                MediaPlayerData.hasActiveMediaOrRecommendationCard()
+            if (!hasActiveMediaOrRecommendationCard && !qsExpanded) {
+                // Skip logging if on LS or QQS, and there is no active media card
+                return
+            }
+            mediaControlPanel?.let {
+                logSmartspaceCardReported(
+                    800, // SMARTSPACE_CARD_SEEN
+                    it.mSmartspaceId,
+                    it.mUid,
+                    intArrayOf(it.surfaceForSmartspaceLogging)
+                )
+                it.mIsImpressed = true
+            }
+        }
+    }
+
+    @JvmOverloads
+    /**
+     * Log Smartspace events
+     *
+     * @param eventId UI event id (e.g. 800 for SMARTSPACE_CARD_SEEN)
+     * @param instanceId id to uniquely identify a card, e.g. each headphone generates a new
+     * instanceId
+     * @param uid uid for the application that media comes from
+     * @param surfaces list of display surfaces the media card is on (e.g. lockscreen, shade) when
+     * the event happened
+     * @param interactedSubcardRank the rank for interacted media item for recommendation card, -1
+     * for tapping on card but not on any media item, 0 for first media item, 1 for second, etc.
+     * @param interactedSubcardCardinality how many media items were shown to the user when there is
+     * user interaction
+     * @param rank the rank for media card in the media carousel, starting from 0
+     * @param receivedLatencyMillis latency in milliseconds for card received events. E.g. latency
+     * between headphone connection to sysUI displays media recommendation card
+     * @param isSwipeToDismiss whether is to log swipe-to-dismiss event
+     */
+    fun logSmartspaceCardReported(
+        eventId: Int,
+        instanceId: Int,
+        uid: Int,
+        surfaces: IntArray,
+        interactedSubcardRank: Int = 0,
+        interactedSubcardCardinality: Int = 0,
+        rank: Int = mediaCarouselScrollHandler.visibleMediaIndex,
+        receivedLatencyMillis: Int = 0,
+        isSwipeToDismiss: Boolean = false
+    ) {
+        if (MediaPlayerData.players().size <= rank) {
+            return
+        }
+
+        val mediaControlKey = MediaPlayerData.visiblePlayerKeys().elementAt(rank)
+        // Only log media resume card when Smartspace data is available
+        if (
+            !mediaControlKey.isSsMediaRec &&
+                !mediaManager.smartspaceMediaData.isActive &&
+                MediaPlayerData.smartspaceMediaData == null
+        ) {
+            return
+        }
+
+        val cardinality = mediaContent.getChildCount()
+        surfaces.forEach { surface ->
+            /* ktlint-disable max-line-length */
+            SysUiStatsLog.write(
+                SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
+                eventId,
+                instanceId,
+                // Deprecated, replaced with AiAi feature type so we don't need to create logging
+                // card type for each new feature.
+                SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD,
+                surface,
+                // Use -1 as rank value to indicate user swipe to dismiss the card
+                if (isSwipeToDismiss) -1 else rank,
+                cardinality,
+                if (mediaControlKey.isSsMediaRec) 15 // MEDIA_RECOMMENDATION
+                else if (mediaControlKey.isSsReactivated) 43 // MEDIA_RESUME_SS_ACTIVATED
+                else 31, // MEDIA_RESUME
+                uid,
+                interactedSubcardRank,
+                interactedSubcardCardinality,
+                receivedLatencyMillis,
+                null, // Media cards cannot have subcards.
+                null // Media cards don't have dimensions today.
+            )
+            /* ktlint-disable max-line-length */
+            if (DEBUG) {
+                Log.d(
+                    TAG,
+                    "Log Smartspace card event id: $eventId instance id: $instanceId" +
+                        " surface: $surface rank: $rank cardinality: $cardinality " +
+                        "isRecommendationCard: ${mediaControlKey.isSsMediaRec} " +
+                        "isSsReactivated: ${mediaControlKey.isSsReactivated}" +
+                        "uid: $uid " +
+                        "interactedSubcardRank: $interactedSubcardRank " +
+                        "interactedSubcardCardinality: $interactedSubcardCardinality " +
+                        "received_latency_millis: $receivedLatencyMillis"
+                )
+            }
+        }
+    }
+
+    private fun onSwipeToDismiss() {
+        MediaPlayerData.players().forEachIndexed { index, it ->
+            if (it.mIsImpressed) {
+                logSmartspaceCardReported(
+                    SMARTSPACE_CARD_DISMISS_EVENT,
+                    it.mSmartspaceId,
+                    it.mUid,
+                    intArrayOf(it.surfaceForSmartspaceLogging),
+                    rank = index,
+                    isSwipeToDismiss = true
+                )
+                // Reset card impressed state when swipe to dismissed
+                it.mIsImpressed = false
+            }
+        }
+        logger.logSwipeDismiss()
+        mediaManager.onSwipeToDismiss()
+    }
+
+    fun getCurrentVisibleMediaContentIntent(): PendingIntent? {
+        return MediaPlayerData.playerKeys()
+            .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
+            ?.data
+            ?.clickIntent
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.apply {
+            println("keysNeedRemoval: $keysNeedRemoval")
+            println("dataKeys: ${MediaPlayerData.dataKeys()}")
+            println("orderedPlayerSortKeys: ${MediaPlayerData.playerKeys()}")
+            println("visiblePlayerSortKeys: ${MediaPlayerData.visiblePlayerKeys()}")
+            println("smartspaceMediaData: ${MediaPlayerData.smartspaceMediaData}")
+            println("shouldPrioritizeSs: ${MediaPlayerData.shouldPrioritizeSs}")
+            println("current size: $currentCarouselWidth x $currentCarouselHeight")
+            println("location: $desiredLocation")
+            println(
+                "state: ${desiredHostState?.expansion}, " +
+                    "only active ${desiredHostState?.showsOnlyActiveMedia}"
+            )
+        }
+    }
+}
+
+@VisibleForTesting
+internal object MediaPlayerData {
+    private val EMPTY =
+        MediaData(
+            userId = -1,
+            initialized = false,
+            app = null,
+            appIcon = null,
+            artist = null,
+            song = null,
+            artwork = null,
+            actions = emptyList(),
+            actionsToShowInCompact = emptyList(),
+            packageName = "INVALID",
+            token = null,
+            clickIntent = null,
+            device = null,
+            active = true,
+            resumeAction = null,
+            instanceId = InstanceId.fakeInstanceId(-1),
+            appUid = -1
+        )
+    // Whether should prioritize Smartspace card.
+    internal var shouldPrioritizeSs: Boolean = false
+        private set
+    internal var smartspaceMediaData: SmartspaceMediaData? = null
+        private set
+
+    data class MediaSortKey(
+        val isSsMediaRec: Boolean, // Whether the item represents a Smartspace media recommendation.
+        val data: MediaData,
+        val key: String,
+        val updateTime: Long = 0,
+        val isSsReactivated: Boolean = false
+    )
+
+    private val comparator =
+        compareByDescending<MediaSortKey> {
+                it.data.isPlaying == true && it.data.playbackLocation == MediaData.PLAYBACK_LOCAL
+            }
+            .thenByDescending {
+                it.data.isPlaying == true &&
+                    it.data.playbackLocation == MediaData.PLAYBACK_CAST_LOCAL
+            }
+            .thenByDescending { it.data.active }
+            .thenByDescending { shouldPrioritizeSs == it.isSsMediaRec }
+            .thenByDescending { !it.data.resumption }
+            .thenByDescending { it.data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE }
+            .thenByDescending { it.data.lastActive }
+            .thenByDescending { it.updateTime }
+            .thenByDescending { it.data.notificationKey }
+
+    private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
+    private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
+    // A map that tracks order of visible media players before they get reordered.
+    private val visibleMediaPlayers = LinkedHashMap<String, MediaSortKey>()
+
+    fun addMediaPlayer(
+        key: String,
+        data: MediaData,
+        player: MediaControlPanel,
+        clock: SystemClock,
+        isSsReactivated: Boolean,
+        debugLogger: MediaCarouselControllerLogger? = null
+    ) {
+        val removedPlayer = removeMediaPlayer(key)
+        if (removedPlayer != null && removedPlayer != player) {
+            debugLogger?.logPotentialMemoryLeak(key)
+        }
+        val sortKey =
+            MediaSortKey(
+                isSsMediaRec = false,
+                data,
+                key,
+                clock.currentTimeMillis(),
+                isSsReactivated = isSsReactivated
+            )
+        mediaData.put(key, sortKey)
+        mediaPlayers.put(sortKey, player)
+        visibleMediaPlayers.put(key, sortKey)
+    }
+
+    fun addMediaRecommendation(
+        key: String,
+        data: SmartspaceMediaData,
+        player: MediaControlPanel,
+        shouldPrioritize: Boolean,
+        clock: SystemClock,
+        debugLogger: MediaCarouselControllerLogger? = null
+    ) {
+        shouldPrioritizeSs = shouldPrioritize
+        val removedPlayer = removeMediaPlayer(key)
+        if (removedPlayer != null && removedPlayer != player) {
+            debugLogger?.logPotentialMemoryLeak(key)
+        }
+        val sortKey =
+            MediaSortKey(
+                isSsMediaRec = true,
+                EMPTY.copy(isPlaying = false),
+                key,
+                clock.currentTimeMillis(),
+                isSsReactivated = true
+            )
+        mediaData.put(key, sortKey)
+        mediaPlayers.put(sortKey, player)
+        visibleMediaPlayers.put(key, sortKey)
+        smartspaceMediaData = data
+    }
+
+    fun moveIfExists(
+        oldKey: String?,
+        newKey: String,
+        debugLogger: MediaCarouselControllerLogger? = null
+    ) {
+        if (oldKey == null || oldKey == newKey) {
+            return
+        }
+
+        mediaData.remove(oldKey)?.let {
+            // MediaPlayer should not be visible
+            // no need to set isDismissed flag.
+            val removedPlayer = removeMediaPlayer(newKey)
+            removedPlayer?.run { debugLogger?.logPotentialMemoryLeak(newKey) }
+            mediaData.put(newKey, it)
+        }
+    }
+
+    fun getMediaControlPanel(visibleIndex: Int): MediaControlPanel? {
+        return mediaPlayers.get(visiblePlayerKeys().elementAt(visibleIndex))
+    }
+
+    fun getMediaPlayer(key: String): MediaControlPanel? {
+        return mediaData.get(key)?.let { mediaPlayers.get(it) }
+    }
+
+    fun getMediaPlayerIndex(key: String): Int {
+        val sortKey = mediaData.get(key)
+        mediaPlayers.entries.forEachIndexed { index, e ->
+            if (e.key == sortKey) {
+                return index
+            }
+        }
+        return -1
+    }
+
+    /**
+     * Removes media player given the key.
+     * @param isDismissed determines whether the media player is removed from the carousel.
+     */
+    fun removeMediaPlayer(key: String, isDismissed: Boolean = false) =
+        mediaData.remove(key)?.let {
+            if (it.isSsMediaRec) {
+                smartspaceMediaData = null
+            }
+            if (isDismissed) {
+                visibleMediaPlayers.remove(key)
+            }
+            mediaPlayers.remove(it)
+        }
+
+    fun mediaData() =
+        mediaData.entries.map { e -> Triple(e.key, e.value.data, e.value.isSsMediaRec) }
+
+    fun dataKeys() = mediaData.keys
+
+    fun players() = mediaPlayers.values
+
+    fun playerKeys() = mediaPlayers.keys
+
+    fun visiblePlayerKeys() = visibleMediaPlayers.values
+
+    /** Returns the index of the first non-timeout media. */
+    fun firstActiveMediaIndex(): Int {
+        mediaPlayers.entries.forEachIndexed { index, e ->
+            if (!e.key.isSsMediaRec && e.key.data.active) {
+                return index
+            }
+        }
+        return -1
+    }
+
+    /** Returns the existing Smartspace target id. */
+    fun smartspaceMediaKey(): String? {
+        mediaData.entries.forEach { e ->
+            if (e.value.isSsMediaRec) {
+                return e.key
+            }
+        }
+        return null
+    }
+
+    @VisibleForTesting
+    fun clear() {
+        mediaData.clear()
+        mediaPlayers.clear()
+        visibleMediaPlayers.clear()
+    }
+
+    /* Returns true if there is active media player card or recommendation card */
+    fun hasActiveMediaOrRecommendationCard(): Boolean {
+        if (smartspaceMediaData != null && smartspaceMediaData?.isActive!!) {
+            return true
+        }
+        if (firstActiveMediaIndex() != -1) {
+            return true
+        }
+        return false
+    }
+
+    fun isSsReactivated(key: String): Boolean = mediaData.get(key)?.isSsReactivated ?: false
+
+    /**
+     * This method is called when media players are reordered. To make sure we have the new version
+     * of the order of media players visible to user.
+     */
+    fun updateVisibleMediaPlayers() {
+        visibleMediaPlayers.clear()
+        playerKeys().forEach { visibleMediaPlayers.put(it.key, it) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
new file mode 100644
index 0000000..eed1bd7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.media.controls.ui
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.MediaCarouselControllerLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import javax.inject.Inject
+
+/** A debug logger for [MediaCarouselController]. */
+@SysUISingleton
+class MediaCarouselControllerLogger
+@Inject
+constructor(@MediaCarouselControllerLog private val buffer: LogBuffer) {
+    /**
+     * Log that there might be a potential memory leak for the [MediaControlPanel] and/or
+     * [MediaViewController] related to [key].
+     */
+    fun logPotentialMemoryLeak(key: String) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { str1 = key },
+            {
+                "Potential memory leak: " +
+                    "Removing control panel for $str1 from map without calling #onDestroy"
+            }
+        )
+
+    fun logMediaLoaded(key: String) =
+        buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "add player $str1" })
+
+    fun logMediaRemoved(key: String) =
+        buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "removing player $str1" })
+
+    fun logRecommendationLoaded(key: String) =
+        buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "add recommendation $str1" })
+
+    fun logRecommendationRemoved(key: String, immediately: Boolean) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = key
+                bool1 = immediately
+            },
+            { "removing recommendation $str1, immediate=$bool1" }
+        )
+}
+
+private const val TAG = "MediaCarouselCtlrLog"
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
similarity index 67%
rename from packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
index a776897..36b2eda 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
 
 import android.graphics.Outline
 import android.util.MathUtils
@@ -31,6 +31,7 @@
 import com.android.systemui.R
 import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.PageIndicator
 import com.android.systemui.util.concurrency.DelayableExecutor
@@ -43,16 +44,13 @@
 private const val SETTINGS_BUTTON_TRANSLATION_FRACTION = 0.3f
 
 /**
- * Default spring configuration to use for animations where stiffness and/or damping ratio
- * were not provided, and a default spring was not set via [PhysicsAnimator.setDefaultSpringConfig].
+ * Default spring configuration to use for animations where stiffness and/or damping ratio were not
+ * provided, and a default spring was not set via [PhysicsAnimator.setDefaultSpringConfig].
  */
-private val translationConfig = PhysicsAnimator.SpringConfig(
-        SpringForce.STIFFNESS_LOW,
-        SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+private val translationConfig =
+    PhysicsAnimator.SpringConfig(SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY)
 
-/**
- * A controller class for the media scrollview, responsible for touch handling
- */
+/** A controller class for the media scrollview, responsible for touch handling */
 class MediaCarouselScrollHandler(
     private val scrollView: MediaScrollView,
     private val pageIndicator: PageIndicator,
@@ -65,57 +63,36 @@
     private val logSmartspaceImpression: (Boolean) -> Unit,
     private val logger: MediaUiEventLogger
 ) {
-    /**
-     * Is the view in RTL
-     */
-    val isRtl: Boolean get() = scrollView.isLayoutRtl
-    /**
-     * Do we need falsing protection?
-     */
+    /** Is the view in RTL */
+    val isRtl: Boolean
+        get() = scrollView.isLayoutRtl
+    /** Do we need falsing protection? */
     var falsingProtectionNeeded: Boolean = false
-    /**
-     * The width of the carousel
-     */
+    /** The width of the carousel */
     private var carouselWidth: Int = 0
 
-    /**
-     * The height of the carousel
-     */
+    /** The height of the carousel */
     private var carouselHeight: Int = 0
 
-    /**
-     * How much are we scrolled into the current media?
-     */
+    /** How much are we scrolled into the current media? */
     private var cornerRadius: Int = 0
 
-    /**
-     * The content where the players are added
-     */
+    /** The content where the players are added */
     private var mediaContent: ViewGroup
-    /**
-     * The gesture detector to detect touch gestures
-     */
+    /** The gesture detector to detect touch gestures */
     private val gestureDetector: GestureDetectorCompat
 
-    /**
-     * The settings button view
-     */
+    /** The settings button view */
     private lateinit var settingsButton: View
 
-    /**
-     * What's the currently visible player index?
-     */
+    /** What's the currently visible player index? */
     var visibleMediaIndex: Int = 0
         private set
 
-    /**
-     * How much are we scrolled into the current media?
-     */
+    /** How much are we scrolled into the current media? */
     private var scrollIntoCurrentMedia: Int = 0
 
-    /**
-     * how much is the content translated in X
-     */
+    /** how much is the content translated in X */
     var contentTranslation = 0.0f
         private set(value) {
             field = value
@@ -125,9 +102,7 @@
             updateClipToOutline()
         }
 
-    /**
-     * The width of a player including padding
-     */
+    /** The width of a player including padding */
     var playerWidthPlusPadding: Int = 0
         set(value) {
             field = value
@@ -135,82 +110,75 @@
             // it's still at the same place
             var newRelativeScroll = visibleMediaIndex * playerWidthPlusPadding
             if (scrollIntoCurrentMedia > playerWidthPlusPadding) {
-                newRelativeScroll += playerWidthPlusPadding -
-                        (scrollIntoCurrentMedia - playerWidthPlusPadding)
+                newRelativeScroll +=
+                    playerWidthPlusPadding - (scrollIntoCurrentMedia - playerWidthPlusPadding)
             } else {
                 newRelativeScroll += scrollIntoCurrentMedia
             }
             scrollView.relativeScrollX = newRelativeScroll
         }
 
-    /**
-     * Does the dismiss currently show the setting cog?
-     */
+    /** Does the dismiss currently show the setting cog? */
     var showsSettingsButton: Boolean = false
 
-    /**
-     * A utility to detect gestures, used in the touch listener
-     */
-    private val gestureListener = object : GestureDetector.SimpleOnGestureListener() {
-        override fun onFling(
-            eStart: MotionEvent?,
-            eCurrent: MotionEvent?,
-            vX: Float,
-            vY: Float
-        ) = onFling(vX, vY)
+    /** A utility to detect gestures, used in the touch listener */
+    private val gestureListener =
+        object : GestureDetector.SimpleOnGestureListener() {
+            override fun onFling(
+                eStart: MotionEvent?,
+                eCurrent: MotionEvent?,
+                vX: Float,
+                vY: Float
+            ) = onFling(vX, vY)
 
-        override fun onScroll(
-            down: MotionEvent?,
-            lastMotion: MotionEvent?,
-            distanceX: Float,
-            distanceY: Float
-        ) = onScroll(down!!, lastMotion!!, distanceX)
+            override fun onScroll(
+                down: MotionEvent?,
+                lastMotion: MotionEvent?,
+                distanceX: Float,
+                distanceY: Float
+            ) = onScroll(down!!, lastMotion!!, distanceX)
 
-        override fun onDown(e: MotionEvent?): Boolean {
-            if (falsingProtectionNeeded) {
-                falsingCollector.onNotificationStartDismissing()
+            override fun onDown(e: MotionEvent?): Boolean {
+                if (falsingProtectionNeeded) {
+                    falsingCollector.onNotificationStartDismissing()
+                }
+                return false
             }
-            return false
         }
-    }
 
-    /**
-     * The touch listener for the scroll view
-     */
-    private val touchListener = object : Gefingerpoken {
-        override fun onTouchEvent(motionEvent: MotionEvent?) = onTouch(motionEvent!!)
-        override fun onInterceptTouchEvent(ev: MotionEvent?) = onInterceptTouch(ev!!)
-    }
+    /** The touch listener for the scroll view */
+    private val touchListener =
+        object : Gefingerpoken {
+            override fun onTouchEvent(motionEvent: MotionEvent?) = onTouch(motionEvent!!)
+            override fun onInterceptTouchEvent(ev: MotionEvent?) = onInterceptTouch(ev!!)
+        }
 
-    /**
-     * A listener that is invoked when the scrolling changes to update player visibilities
-     */
-    private val scrollChangedListener = object : View.OnScrollChangeListener {
-        override fun onScrollChange(
-            v: View?,
-            scrollX: Int,
-            scrollY: Int,
-            oldScrollX: Int,
-            oldScrollY: Int
-        ) {
-            if (playerWidthPlusPadding == 0) {
-                return
+    /** A listener that is invoked when the scrolling changes to update player visibilities */
+    private val scrollChangedListener =
+        object : View.OnScrollChangeListener {
+            override fun onScrollChange(
+                v: View?,
+                scrollX: Int,
+                scrollY: Int,
+                oldScrollX: Int,
+                oldScrollY: Int
+            ) {
+                if (playerWidthPlusPadding == 0) {
+                    return
+                }
+
+                val relativeScrollX = scrollView.relativeScrollX
+                onMediaScrollingChanged(
+                    relativeScrollX / playerWidthPlusPadding,
+                    relativeScrollX % playerWidthPlusPadding
+                )
             }
-
-            val relativeScrollX = scrollView.relativeScrollX
-            onMediaScrollingChanged(relativeScrollX / playerWidthPlusPadding,
-                    relativeScrollX % playerWidthPlusPadding)
         }
-    }
 
-    /**
-     * Whether the media card is visible to user if any
-     */
+    /** Whether the media card is visible to user if any */
     var visibleToUser: Boolean = false
 
-    /**
-     * Whether the quick setting is expanded or not
-     */
+    /** Whether the quick setting is expanded or not */
     var qsExpanded: Boolean = false
 
     init {
@@ -219,47 +187,61 @@
         scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER)
         mediaContent = scrollView.contentContainer
         scrollView.setOnScrollChangeListener(scrollChangedListener)
-        scrollView.outlineProvider = object : ViewOutlineProvider() {
-            override fun getOutline(view: View?, outline: Outline?) {
-                outline?.setRoundRect(0, 0, carouselWidth, carouselHeight, cornerRadius.toFloat())
+        scrollView.outlineProvider =
+            object : ViewOutlineProvider() {
+                override fun getOutline(view: View?, outline: Outline?) {
+                    outline?.setRoundRect(
+                        0,
+                        0,
+                        carouselWidth,
+                        carouselHeight,
+                        cornerRadius.toFloat()
+                    )
+                }
             }
-        }
     }
 
     fun onSettingsButtonUpdated(button: View) {
         settingsButton = button
         // We don't have a context to resolve, lets use the settingsbuttons one since that is
         // reinflated appropriately
-        cornerRadius = settingsButton.resources.getDimensionPixelSize(
-                Utils.getThemeAttr(settingsButton.context, android.R.attr.dialogCornerRadius))
+        cornerRadius =
+            settingsButton.resources.getDimensionPixelSize(
+                Utils.getThemeAttr(settingsButton.context, android.R.attr.dialogCornerRadius)
+            )
         updateSettingsPresentation()
         scrollView.invalidateOutline()
     }
 
     private fun updateSettingsPresentation() {
         if (showsSettingsButton && settingsButton.width > 0) {
-            val settingsOffset = MathUtils.map(
+            val settingsOffset =
+                MathUtils.map(
                     0.0f,
                     getMaxTranslation().toFloat(),
                     0.0f,
                     1.0f,
-                    Math.abs(contentTranslation))
-            val settingsTranslation = (1.0f - settingsOffset) * -settingsButton.width *
+                    Math.abs(contentTranslation)
+                )
+            val settingsTranslation =
+                (1.0f - settingsOffset) *
+                    -settingsButton.width *
                     SETTINGS_BUTTON_TRANSLATION_FRACTION
-            val newTranslationX = if (isRtl) {
-                // In RTL, the 0-placement is on the right side of the view, not the left...
-                if (contentTranslation > 0) {
-                    -(scrollView.width - settingsTranslation - settingsButton.width)
+            val newTranslationX =
+                if (isRtl) {
+                    // In RTL, the 0-placement is on the right side of the view, not the left...
+                    if (contentTranslation > 0) {
+                        -(scrollView.width - settingsTranslation - settingsButton.width)
+                    } else {
+                        -settingsTranslation
+                    }
                 } else {
-                    -settingsTranslation
+                    if (contentTranslation > 0) {
+                        settingsTranslation
+                    } else {
+                        scrollView.width - settingsTranslation - settingsButton.width
+                    }
                 }
-            } else {
-                if (contentTranslation > 0) {
-                    settingsTranslation
-                } else {
-                    scrollView.width - settingsTranslation - settingsButton.width
-                }
-            }
             val rotation = (1.0f - settingsOffset) * 50
             settingsButton.rotation = rotation * -Math.signum(contentTranslation)
             val alpha = MathUtils.saturate(MathUtils.map(0.5f, 1.0f, 0.0f, 1.0f, settingsOffset))
@@ -306,16 +288,14 @@
                 val newScrollX = scrollView.relativeScrollX + dx
                 // Delay the scrolling since scrollView calls springback which cancels
                 // the animation again..
-                mainExecutor.execute {
-                    scrollView.smoothScrollTo(newScrollX, scrollView.scrollY)
-                }
+                mainExecutor.execute { scrollView.smoothScrollTo(newScrollX, scrollView.scrollY) }
             }
             val currentTranslation = scrollView.getContentTranslation()
             if (currentTranslation != 0.0f) {
                 // We started a Swipe but didn't end up with a fling. Let's either go to the
                 // dismissed position or go back.
-                val springBack = Math.abs(currentTranslation) < getMaxTranslation() / 2 ||
-                        isFalseTouch()
+                val springBack =
+                    Math.abs(currentTranslation) < getMaxTranslation() / 2 || isFalseTouch()
                 val newTranslation: Float
                 if (springBack) {
                     newTranslation = 0.0f
@@ -324,13 +304,17 @@
                     if (!showsSettingsButton) {
                         // Delay the dismiss a bit to avoid too much overlap. Waiting until the
                         // animation has finished also feels a bit too slow here.
-                        mainExecutor.executeDelayed({
-                            dismissCallback.invoke()
-                        }, DISMISS_DELAY)
+                        mainExecutor.executeDelayed({ dismissCallback.invoke() }, DISMISS_DELAY)
                     }
                 }
-                PhysicsAnimator.getInstance(this).spring(CONTENT_TRANSLATION,
-                        newTranslation, startVelocity = 0.0f, config = translationConfig).start()
+                PhysicsAnimator.getInstance(this)
+                    .spring(
+                        CONTENT_TRANSLATION,
+                        newTranslation,
+                        startVelocity = 0.0f,
+                        config = translationConfig
+                    )
+                    .start()
                 scrollView.animationTargetX = newTranslation
             }
         }
@@ -338,10 +322,11 @@
         return false
     }
 
-    private fun isFalseTouch() = falsingProtectionNeeded &&
-            falsingManager.isFalseTouch(NOTIFICATION_DISMISS)
+    private fun isFalseTouch() =
+        falsingProtectionNeeded && falsingManager.isFalseTouch(NOTIFICATION_DISMISS)
 
-    private fun getMaxTranslation() = if (showsSettingsButton) {
+    private fun getMaxTranslation() =
+        if (showsSettingsButton) {
             settingsButton.width
         } else {
             playerWidthPlusPadding
@@ -351,15 +336,10 @@
         return gestureDetector.onTouchEvent(motionEvent)
     }
 
-    fun onScroll(
-        down: MotionEvent,
-        lastMotion: MotionEvent,
-        distanceX: Float
-    ): Boolean {
+    fun onScroll(down: MotionEvent, lastMotion: MotionEvent, distanceX: Float): Boolean {
         val totalX = lastMotion.x - down.x
         val currentTranslation = scrollView.getContentTranslation()
-        if (currentTranslation != 0.0f ||
-                !scrollView.canScrollHorizontally((-totalX).toInt())) {
+        if (currentTranslation != 0.0f || !scrollView.canScrollHorizontally((-totalX).toInt())) {
             var newTranslation = currentTranslation - distanceX
             val absTranslation = Math.abs(newTranslation)
             if (absTranslation > getMaxTranslation()) {
@@ -373,14 +353,18 @@
                         newTranslation = currentTranslation - distanceX * RUBBERBAND_FACTOR
                     } else {
                         // We just crossed the boundary, let's rubberband it all
-                        newTranslation = Math.signum(newTranslation) * (getMaxTranslation() +
-                                (absTranslation - getMaxTranslation()) * RUBBERBAND_FACTOR)
+                        newTranslation =
+                            Math.signum(newTranslation) *
+                                (getMaxTranslation() +
+                                    (absTranslation - getMaxTranslation()) * RUBBERBAND_FACTOR)
                     }
                 } // Otherwise we don't have do do anything, and will remove the unrubberbanded
                 // translation
             }
-            if (Math.signum(newTranslation) != Math.signum(currentTranslation) &&
-                    currentTranslation != 0.0f) {
+            if (
+                Math.signum(newTranslation) != Math.signum(currentTranslation) &&
+                    currentTranslation != 0.0f
+            ) {
                 // We crossed the 0.0 threshold of the translation. Let's see if we're allowed
                 // to scroll into the new direction
                 if (scrollView.canScrollHorizontally(-newTranslation.toInt())) {
@@ -391,8 +375,14 @@
             }
             val physicsAnimator = PhysicsAnimator.getInstance(this)
             if (physicsAnimator.isRunning()) {
-                physicsAnimator.spring(CONTENT_TRANSLATION,
-                        newTranslation, startVelocity = 0.0f, config = translationConfig).start()
+                physicsAnimator
+                    .spring(
+                        CONTENT_TRANSLATION,
+                        newTranslation,
+                        startVelocity = 0.0f,
+                        config = translationConfig
+                    )
+                    .start()
             } else {
                 contentTranslation = newTranslation
             }
@@ -402,10 +392,7 @@
         return false
     }
 
-    private fun onFling(
-        vX: Float,
-        vY: Float
-    ): Boolean {
+    private fun onFling(vX: Float, vY: Float): Boolean {
         if (vX * vX < 0.5 * vY * vY) {
             return false
         }
@@ -424,13 +411,17 @@
                 // Delay the dismiss a bit to avoid too much overlap. Waiting until the animation
                 // has finished also feels a bit too slow here.
                 if (!showsSettingsButton) {
-                    mainExecutor.executeDelayed({
-                        dismissCallback.invoke()
-                    }, DISMISS_DELAY)
+                    mainExecutor.executeDelayed({ dismissCallback.invoke() }, DISMISS_DELAY)
                 }
             }
-            PhysicsAnimator.getInstance(this).spring(CONTENT_TRANSLATION,
-                    newTranslation, startVelocity = vX, config = translationConfig).start()
+            PhysicsAnimator.getInstance(this)
+                .spring(
+                    CONTENT_TRANSLATION,
+                    newTranslation,
+                    startVelocity = vX,
+                    config = translationConfig
+                )
+                .start()
             scrollView.animationTargetX = newTranslation
         } else {
             // We're flinging the player! Let's go either to the previous or to the next player
@@ -443,21 +434,18 @@
             val view = mediaContent.getChildAt(destIndex)
             // We need to post this since we're dispatching a touch to the underlying view to cancel
             // but canceling will actually abort the animation.
-            mainExecutor.execute {
-                scrollView.smoothScrollTo(view.left, scrollView.scrollY)
-            }
+            mainExecutor.execute { scrollView.smoothScrollTo(view.left, scrollView.scrollY) }
         }
         return true
     }
 
-    /**
-     * Reset the translation of the players when swiped
-     */
+    /** Reset the translation of the players when swiped */
     fun resetTranslation(animate: Boolean = false) {
         if (scrollView.getContentTranslation() != 0.0f) {
             if (animate) {
-                PhysicsAnimator.getInstance(this).spring(CONTENT_TRANSLATION,
-                        0.0f, config = translationConfig).start()
+                PhysicsAnimator.getInstance(this)
+                    .spring(CONTENT_TRANSLATION, 0.0f, config = translationConfig)
+                    .start()
                 scrollView.animationTargetX = 0.0f
             } else {
                 PhysicsAnimator.getInstance(this).cancel()
@@ -485,21 +473,22 @@
             closeGuts(false)
             updatePlayerVisibilities()
         }
-        val relativeLocation = visibleMediaIndex.toFloat() + if (playerWidthPlusPadding > 0)
-            scrollInAmount.toFloat() / playerWidthPlusPadding else 0f
+        val relativeLocation =
+            visibleMediaIndex.toFloat() +
+                if (playerWidthPlusPadding > 0) scrollInAmount.toFloat() / playerWidthPlusPadding
+                else 0f
         // Fix the location, because PageIndicator does not handle RTL internally
-        val location = if (isRtl) {
-            mediaContent.childCount - relativeLocation - 1
-        } else {
-            relativeLocation
-        }
+        val location =
+            if (isRtl) {
+                mediaContent.childCount - relativeLocation - 1
+            } else {
+                relativeLocation
+            }
         pageIndicator.setLocation(location)
         updateClipToOutline()
     }
 
-    /**
-     * Notified whenever the players or their order has changed
-     */
+    /** Notified whenever the players or their order has changed */
     fun onPlayersChanged() {
         updatePlayerVisibilities()
         updateMediaPaddings()
@@ -529,8 +518,8 @@
     }
 
     /**
-     * Notify that a player will be removed right away. This gives us the opporunity to look
-     * where it was and update our scroll position.
+     * Notify that a player will be removed right away. This gives us the opporunity to look where
+     * it was and update our scroll position.
      */
     fun onPrePlayerRemoved(removed: MediaControlPanel) {
         val removedIndex = mediaContent.indexOfChild(removed.mediaViewHolder?.player)
@@ -550,9 +539,7 @@
         }
     }
 
-    /**
-     * Update the bounds of the carousel
-     */
+    /** Update the bounds of the carousel */
     fun setCarouselBounds(currentCarouselWidth: Int, currentCarouselHeight: Int) {
         if (currentCarouselHeight != carouselHeight || currentCarouselWidth != carouselHeight) {
             carouselWidth = currentCarouselWidth
@@ -561,9 +548,7 @@
         }
     }
 
-    /**
-     * Reset the MediaScrollView to the start.
-     */
+    /** Reset the MediaScrollView to the start. */
     fun scrollToStart() {
         scrollView.relativeScrollX = 0
     }
@@ -581,21 +566,22 @@
         val destIndex = Math.min(mediaContent.getChildCount() - 1, destIndex)
         val view = mediaContent.getChildAt(destIndex)
         // We need to post this to wait for the active player becomes visible.
-        mainExecutor.executeDelayed({
-            scrollView.smoothScrollTo(view.left, scrollView.scrollY)
-        }, SCROLL_DELAY)
+        mainExecutor.executeDelayed(
+            { scrollView.smoothScrollTo(view.left, scrollView.scrollY) },
+            SCROLL_DELAY
+        )
     }
 
     companion object {
-        private val CONTENT_TRANSLATION = object : FloatPropertyCompat<MediaCarouselScrollHandler>(
-                "contentTranslation") {
-            override fun getValue(handler: MediaCarouselScrollHandler): Float {
-                return handler.contentTranslation
-            }
+        private val CONTENT_TRANSLATION =
+            object : FloatPropertyCompat<MediaCarouselScrollHandler>("contentTranslation") {
+                override fun getValue(handler: MediaCarouselScrollHandler): Float {
+                    return handler.contentTranslation
+                }
 
-            override fun setValue(handler: MediaCarouselScrollHandler, value: Float) {
-                handler.contentTranslation = value
+                override fun setValue(handler: MediaCarouselScrollHandler, value: Float) {
+                    handler.contentTranslation = value
+                }
             }
-        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaColorSchemes.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/media/MediaColorSchemes.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt
index 208766d..82abf9b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaColorSchemes.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
 
 import com.android.systemui.monet.ColorScheme
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 759795f..18ecadb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media;
+package com.android.systemui.media.controls.ui;
 
 import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS;
 
-import static com.android.systemui.media.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS;
+import static com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS;
 
 import android.animation.Animator;
 import android.animation.AnimatorInflater;
@@ -76,6 +76,20 @@
 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.util.MediaDataUtils;
+import com.android.systemui.media.controls.util.MediaUiEventLogger;
+import com.android.systemui.media.controls.util.SmallHash;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.monet.ColorScheme;
 import com.android.systemui.monet.Style;
@@ -111,7 +125,6 @@
             "com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT";
     private static final String KEY_SMARTSPACE_ARTIST_NAME = "artist_name";
     private static final String KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND";
-    protected static final String KEY_SMARTSPACE_APP_NAME = "KEY_SMARTSPACE_APP_NAME";
 
     // Event types logged by smartspace
     private static final int SMARTSPACE_CARD_CLICK_EVENT = 760;
@@ -251,6 +264,9 @@
         });
     }
 
+    /**
+     * Clean up seekbar and controller when panel is destroyed
+     */
     public void onDestroy() {
         if (mSeekBarObserver != null) {
             mSeekBarViewModel.getProgress().removeObserver(mSeekBarObserver);
@@ -651,7 +667,7 @@
             return;
         }
 
-       CharSequence contentDescription;
+        CharSequence contentDescription;
         if (mMediaViewController.isGutsVisible()) {
             contentDescription =
                     mRecommendationViewHolder.getGutsViewHolder().getGutsText().getText();
@@ -1441,7 +1457,7 @@
             }
 
             // Automatically scroll to the active player once the media is loaded.
-            mMediaCarouselController.setShouldScrollToActivePlayer(true);
+            mMediaCarouselController.setShouldScrollToKey(true);
         });
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
new file mode 100644
index 0000000..6b46d8f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -0,0 +1,1272 @@
+/*
+ * Copyright (C) 2020 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.media.controls.ui
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.annotation.IntDef
+import android.content.Context
+import android.content.res.Configuration
+import android.database.ContentObserver
+import android.graphics.Rect
+import android.net.Uri
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings
+import android.util.Log
+import android.util.MathUtils
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroupOverlay
+import androidx.annotation.VisibleForTesting
+import com.android.keyguard.KeyguardViewController
+import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dreams.DreamOverlayStateController
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.media.dream.MediaDreamComplication
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.NotifPanelEvents
+import com.android.systemui.statusbar.CrossFadeHelper
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.LargeScreenUtils
+import com.android.systemui.util.animation.UniqueObjectHostView
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.traceSection
+import javax.inject.Inject
+
+private val TAG: String = MediaHierarchyManager::class.java.simpleName
+
+/** Similarly to isShown but also excludes views that have 0 alpha */
+val View.isShownNotFaded: Boolean
+    get() {
+        var current: View = this
+        while (true) {
+            if (current.visibility != View.VISIBLE) {
+                return false
+            }
+            if (current.alpha == 0.0f) {
+                return false
+            }
+            val parent = current.parent ?: return false // We are not attached to the view root
+            if (parent !is View) {
+                // we reached the viewroot, hurray
+                return true
+            }
+            current = parent
+        }
+    }
+
+/**
+ * This manager is responsible for placement of the unique media view between the different hosts
+ * and animate the positions of the views to achieve seamless transitions.
+ */
+@SysUISingleton
+class MediaHierarchyManager
+@Inject
+constructor(
+    private val context: Context,
+    private val statusBarStateController: SysuiStatusBarStateController,
+    private val keyguardStateController: KeyguardStateController,
+    private val bypassController: KeyguardBypassController,
+    private val mediaCarouselController: MediaCarouselController,
+    private val keyguardViewController: KeyguardViewController,
+    private val dreamOverlayStateController: DreamOverlayStateController,
+    configurationController: ConfigurationController,
+    wakefulnessLifecycle: WakefulnessLifecycle,
+    panelEventsEvents: NotifPanelEvents,
+    private val secureSettings: SecureSettings,
+    @Main private val handler: Handler,
+) {
+
+    /** Track the media player setting status on lock screen. */
+    private var allowMediaPlayerOnLockScreen: Boolean = true
+    private val lockScreenMediaPlayerUri =
+        secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN)
+
+    /**
+     * Whether we "skip" QQS during panel expansion.
+     *
+     * This means that when expanding the panel we go directly to QS. Also when we are on QS and
+     * start closing the panel, it fully collapses instead of going to QQS.
+     */
+    private var skipQqsOnExpansion: Boolean = false
+
+    /**
+     * The root overlay of the hierarchy. This is where the media notification is attached to
+     * whenever the view is transitioning from one host to another. It also make sure that the view
+     * is always in its final state when it is attached to a view host.
+     */
+    private var rootOverlay: ViewGroupOverlay? = null
+
+    private var rootView: View? = null
+    private var currentBounds = Rect()
+    private var animationStartBounds: Rect = Rect()
+
+    private var animationStartClipping = Rect()
+    private var currentClipping = Rect()
+    private var targetClipping = Rect()
+
+    /**
+     * The cross fade progress at the start of the animation. 0.5f means it's just switching between
+     * the start and the end location and the content is fully faded, while 0.75f means that we're
+     * halfway faded in again in the target state.
+     */
+    private var animationStartCrossFadeProgress = 0.0f
+
+    /** The starting alpha of the animation */
+    private var animationStartAlpha = 0.0f
+
+    /** The starting location of the cross fade if an animation is running right now. */
+    @MediaLocation private var crossFadeAnimationStartLocation = -1
+
+    /** The end location of the cross fade if an animation is running right now. */
+    @MediaLocation private var crossFadeAnimationEndLocation = -1
+    private var targetBounds: Rect = Rect()
+    private val mediaFrame
+        get() = mediaCarouselController.mediaFrame
+    private var statusbarState: Int = statusBarStateController.state
+    private var animator =
+        ValueAnimator.ofFloat(0.0f, 1.0f).apply {
+            interpolator = Interpolators.FAST_OUT_SLOW_IN
+            addUpdateListener {
+                updateTargetState()
+                val currentAlpha: Float
+                var boundsProgress = animatedFraction
+                if (isCrossFadeAnimatorRunning) {
+                    animationCrossFadeProgress =
+                        MathUtils.lerp(animationStartCrossFadeProgress, 1.0f, animatedFraction)
+                    // When crossfading, let's keep the bounds at the right location during fading
+                    boundsProgress = if (animationCrossFadeProgress < 0.5f) 0.0f else 1.0f
+                    currentAlpha = calculateAlphaFromCrossFade(animationCrossFadeProgress)
+                } else {
+                    // If we're not crossfading, let's interpolate from the start alpha to 1.0f
+                    currentAlpha = MathUtils.lerp(animationStartAlpha, 1.0f, animatedFraction)
+                }
+                interpolateBounds(
+                    animationStartBounds,
+                    targetBounds,
+                    boundsProgress,
+                    result = currentBounds
+                )
+                resolveClipping(currentClipping)
+                applyState(currentBounds, currentAlpha, clipBounds = currentClipping)
+            }
+            addListener(
+                object : AnimatorListenerAdapter() {
+                    private var cancelled: Boolean = false
+
+                    override fun onAnimationCancel(animation: Animator?) {
+                        cancelled = true
+                        animationPending = false
+                        rootView?.removeCallbacks(startAnimation)
+                    }
+
+                    override fun onAnimationEnd(animation: Animator?) {
+                        isCrossFadeAnimatorRunning = false
+                        if (!cancelled) {
+                            applyTargetStateIfNotAnimating()
+                        }
+                    }
+
+                    override fun onAnimationStart(animation: Animator?) {
+                        cancelled = false
+                        animationPending = false
+                    }
+                }
+            )
+        }
+
+    private fun resolveClipping(result: Rect) {
+        if (animationStartClipping.isEmpty) result.set(targetClipping)
+        else if (targetClipping.isEmpty) result.set(animationStartClipping)
+        else result.setIntersect(animationStartClipping, targetClipping)
+    }
+
+    private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_DREAM_OVERLAY + 1)
+    /**
+     * The last location where this view was at before going to the desired location. This is useful
+     * for guided transitions.
+     */
+    @MediaLocation private var previousLocation = -1
+    /** The desired location where the view will be at the end of the transition. */
+    @MediaLocation private var desiredLocation = -1
+
+    /**
+     * The current attachment location where the view is currently attached. Usually this matches
+     * the desired location except for animations whenever a view moves to the new desired location,
+     * during which it is in [IN_OVERLAY].
+     */
+    @MediaLocation private var currentAttachmentLocation = -1
+
+    private var inSplitShade = false
+
+    /** Is there any active media in the carousel? */
+    private var hasActiveMedia: Boolean = false
+        get() = mediaHosts.get(LOCATION_QQS)?.visible == true
+
+    /** Are we currently waiting on an animation to start? */
+    private var animationPending: Boolean = false
+    private val startAnimation: Runnable = Runnable { animator.start() }
+
+    /** The expansion of quick settings */
+    var qsExpansion: Float = 0.0f
+        set(value) {
+            if (field != value) {
+                field = value
+                updateDesiredLocation()
+                if (getQSTransformationProgress() >= 0) {
+                    updateTargetState()
+                    applyTargetStateIfNotAnimating()
+                }
+            }
+        }
+
+    /** Is quick setting expanded? */
+    var qsExpanded: Boolean = false
+        set(value) {
+            if (field != value) {
+                field = value
+                mediaCarouselController.mediaCarouselScrollHandler.qsExpanded = value
+            }
+            // qs is expanded on LS shade and HS shade
+            if (value && (isLockScreenShadeVisibleToUser() || isHomeScreenShadeVisibleToUser())) {
+                mediaCarouselController.logSmartspaceImpression(value)
+            }
+            mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
+        }
+
+    /**
+     * distance that the full shade transition takes in order for media to fully transition to the
+     * shade
+     */
+    private var distanceForFullShadeTransition = 0
+
+    /**
+     * The amount of progress we are currently in if we're transitioning to the full shade. 0.0f
+     * means we're not transitioning yet, while 1 means we're all the way in the full shade.
+     */
+    private var fullShadeTransitionProgress = 0f
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            if (bypassController.bypassEnabled || statusbarState != StatusBarState.KEYGUARD) {
+                // No need to do all the calculations / updates below if we're not on the lockscreen
+                // or if we're bypassing.
+                return
+            }
+            updateDesiredLocation(forceNoAnimation = isCurrentlyFading())
+            if (value >= 0) {
+                updateTargetState()
+                // Setting the alpha directly, as the below call will use it to update the alpha
+                carouselAlpha = calculateAlphaFromCrossFade(field)
+                applyTargetStateIfNotAnimating()
+            }
+        }
+
+    /** Is there currently a cross-fade animation running driven by an animator? */
+    private var isCrossFadeAnimatorRunning = false
+
+    /**
+     * Are we currently transitionioning from the lockscreen to the full shade
+     * [StatusBarState.SHADE_LOCKED] or [StatusBarState.SHADE]. Once the user has dragged down and
+     * the transition starts, this will no longer return true.
+     */
+    private val isTransitioningToFullShade: Boolean
+        get() =
+            fullShadeTransitionProgress != 0f &&
+                !bypassController.bypassEnabled &&
+                statusbarState == StatusBarState.KEYGUARD
+
+    /**
+     * Set the amount of pixels we have currently dragged down if we're transitioning to the full
+     * shade. 0.0f means we're not transitioning yet.
+     */
+    fun setTransitionToFullShadeAmount(value: Float) {
+        // If we're transitioning starting on the shade_locked, we don't want any delay and rather
+        // have it aligned with the rest of the animation
+        val progress = MathUtils.saturate(value / distanceForFullShadeTransition)
+        fullShadeTransitionProgress = progress
+    }
+
+    /**
+     * Returns the amount of translationY of the media container, during the current guided
+     * transformation, if running. If there is no guided transformation running, it will return 0.
+     */
+    fun getGuidedTransformationTranslationY(): Int {
+        if (!isCurrentlyInGuidedTransformation()) {
+            return -1
+        }
+        val startHost = getHost(previousLocation) ?: return 0
+        return targetBounds.top - startHost.currentBounds.top
+    }
+
+    /**
+     * Is the shade currently collapsing from the expanded qs? If we're on the lockscreen and in qs,
+     * we wouldn't want to transition in that case.
+     */
+    var collapsingShadeFromQS: Boolean = false
+        set(value) {
+            if (field != value) {
+                field = value
+                updateDesiredLocation(forceNoAnimation = true)
+            }
+        }
+
+    /** Are location changes currently blocked? */
+    private val blockLocationChanges: Boolean
+        get() {
+            return goingToSleep || dozeAnimationRunning
+        }
+
+    /** Are we currently going to sleep */
+    private var goingToSleep: Boolean = false
+        set(value) {
+            if (field != value) {
+                field = value
+                if (!value) {
+                    updateDesiredLocation()
+                }
+            }
+        }
+
+    /** Are we currently fullyAwake */
+    private var fullyAwake: Boolean = false
+        set(value) {
+            if (field != value) {
+                field = value
+                if (value) {
+                    updateDesiredLocation(forceNoAnimation = true)
+                }
+            }
+        }
+
+    /** Is the doze animation currently Running */
+    private var dozeAnimationRunning: Boolean = false
+        private set(value) {
+            if (field != value) {
+                field = value
+                if (!value) {
+                    updateDesiredLocation()
+                }
+            }
+        }
+
+    /** Is the dream overlay currently active */
+    private var dreamOverlayActive: Boolean = false
+        private set(value) {
+            if (field != value) {
+                field = value
+                updateDesiredLocation(forceNoAnimation = true)
+            }
+        }
+
+    /** Is the dream media complication currently active */
+    private var dreamMediaComplicationActive: Boolean = false
+        private set(value) {
+            if (field != value) {
+                field = value
+                updateDesiredLocation(forceNoAnimation = true)
+            }
+        }
+
+    /**
+     * The current cross fade progress. 0.5f means it's just switching between the start and the end
+     * location and the content is fully faded, while 0.75f means that we're halfway faded in again
+     * in the target state. This is only valid while [isCrossFadeAnimatorRunning] is true.
+     */
+    private var animationCrossFadeProgress = 1.0f
+
+    /** The current carousel Alpha. */
+    private var carouselAlpha: Float = 1.0f
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            CrossFadeHelper.fadeIn(mediaFrame, value)
+        }
+
+    /**
+     * Calculate the alpha of the view when given a cross-fade progress.
+     *
+     * @param crossFadeProgress The current cross fade progress. 0.5f means it's just switching
+     * between the start and the end location and the content is fully faded, while 0.75f means that
+     * we're halfway faded in again in the target state.
+     */
+    private fun calculateAlphaFromCrossFade(crossFadeProgress: Float): Float {
+        if (crossFadeProgress <= 0.5f) {
+            return 1.0f - crossFadeProgress / 0.5f
+        } else {
+            return (crossFadeProgress - 0.5f) / 0.5f
+        }
+    }
+
+    init {
+        updateConfiguration()
+        configurationController.addCallback(
+            object : ConfigurationController.ConfigurationListener {
+                override fun onConfigChanged(newConfig: Configuration?) {
+                    updateConfiguration()
+                    updateDesiredLocation(forceNoAnimation = true, forceStateUpdate = true)
+                }
+            }
+        )
+        statusBarStateController.addCallback(
+            object : StatusBarStateController.StateListener {
+                override fun onStatePreChange(oldState: Int, newState: Int) {
+                    // We're updating the location before the state change happens, since we want
+                    // the
+                    // location of the previous state to still be up to date when the animation
+                    // starts
+                    statusbarState = newState
+                    updateDesiredLocation()
+                }
+
+                override fun onStateChanged(newState: Int) {
+                    updateTargetState()
+                    // Enters shade from lock screen
+                    if (
+                        newState == StatusBarState.SHADE_LOCKED && isLockScreenShadeVisibleToUser()
+                    ) {
+                        mediaCarouselController.logSmartspaceImpression(qsExpanded)
+                    }
+                    mediaCarouselController.mediaCarouselScrollHandler.visibleToUser =
+                        isVisibleToUser()
+                }
+
+                override fun onDozeAmountChanged(linear: Float, eased: Float) {
+                    dozeAnimationRunning = linear != 0.0f && linear != 1.0f
+                }
+
+                override fun onDozingChanged(isDozing: Boolean) {
+                    if (!isDozing) {
+                        dozeAnimationRunning = false
+                        // Enters lock screen from screen off
+                        if (isLockScreenVisibleToUser()) {
+                            mediaCarouselController.logSmartspaceImpression(qsExpanded)
+                        }
+                    } else {
+                        updateDesiredLocation()
+                        qsExpanded = false
+                        closeGuts()
+                    }
+                    mediaCarouselController.mediaCarouselScrollHandler.visibleToUser =
+                        isVisibleToUser()
+                }
+
+                override fun onExpandedChanged(isExpanded: Boolean) {
+                    // Enters shade from home screen
+                    if (isHomeScreenShadeVisibleToUser()) {
+                        mediaCarouselController.logSmartspaceImpression(qsExpanded)
+                    }
+                    mediaCarouselController.mediaCarouselScrollHandler.visibleToUser =
+                        isVisibleToUser()
+                }
+            }
+        )
+
+        dreamOverlayStateController.addCallback(
+            object : DreamOverlayStateController.Callback {
+                override fun onComplicationsChanged() {
+                    dreamMediaComplicationActive =
+                        dreamOverlayStateController.complications.any {
+                            it is MediaDreamComplication
+                        }
+                }
+
+                override fun onStateChanged() {
+                    dreamOverlayStateController.isOverlayActive.also { dreamOverlayActive = it }
+                }
+            }
+        )
+
+        wakefulnessLifecycle.addObserver(
+            object : WakefulnessLifecycle.Observer {
+                override fun onFinishedGoingToSleep() {
+                    goingToSleep = false
+                }
+
+                override fun onStartedGoingToSleep() {
+                    goingToSleep = true
+                    fullyAwake = false
+                }
+
+                override fun onFinishedWakingUp() {
+                    goingToSleep = false
+                    fullyAwake = true
+                }
+
+                override fun onStartedWakingUp() {
+                    goingToSleep = false
+                }
+            }
+        )
+
+        mediaCarouselController.updateUserVisibility = {
+            mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
+        }
+        mediaCarouselController.updateHostVisibility = {
+            mediaHosts.forEach { it?.updateViewVisibility() }
+        }
+
+        panelEventsEvents.registerListener(
+            object : NotifPanelEvents.Listener {
+                override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {
+                    skipQqsOnExpansion = isExpandImmediateEnabled
+                    updateDesiredLocation()
+                }
+            }
+        )
+
+        val settingsObserver: ContentObserver =
+            object : ContentObserver(handler) {
+                override fun onChange(selfChange: Boolean, uri: Uri?) {
+                    if (uri == lockScreenMediaPlayerUri) {
+                        allowMediaPlayerOnLockScreen =
+                            secureSettings.getBoolForUser(
+                                Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
+                                true,
+                                UserHandle.USER_CURRENT
+                            )
+                    }
+                }
+            }
+        secureSettings.registerContentObserverForUser(
+            Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
+            settingsObserver,
+            UserHandle.USER_ALL
+        )
+    }
+
+    private fun updateConfiguration() {
+        distanceForFullShadeTransition =
+            context.resources.getDimensionPixelSize(
+                R.dimen.lockscreen_shade_media_transition_distance
+            )
+        inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(context.resources)
+    }
+
+    /**
+     * Register a media host and create a view can be attached to a view hierarchy and where the
+     * players will be placed in when the host is the currently desired state.
+     *
+     * @return the hostView associated with this location
+     */
+    fun register(mediaObject: MediaHost): UniqueObjectHostView {
+        val viewHost = createUniqueObjectHost()
+        mediaObject.hostView = viewHost
+        mediaObject.addVisibilityChangeListener {
+            // If QQS changes visibility, we need to force an update to ensure the transition
+            // goes into the correct state
+            val stateUpdate = mediaObject.location == LOCATION_QQS
+
+            // Never animate because of a visibility change, only state changes should do that
+            updateDesiredLocation(forceNoAnimation = true, forceStateUpdate = stateUpdate)
+        }
+        mediaHosts[mediaObject.location] = mediaObject
+        if (mediaObject.location == desiredLocation) {
+            // In case we are overriding a view that is already visible, make sure we attach it
+            // to this new host view in the below call
+            desiredLocation = -1
+        }
+        if (mediaObject.location == currentAttachmentLocation) {
+            currentAttachmentLocation = -1
+        }
+        updateDesiredLocation()
+        return viewHost
+    }
+
+    /** Close the guts in all players in [MediaCarouselController]. */
+    fun closeGuts() {
+        mediaCarouselController.closeGuts()
+    }
+
+    private fun createUniqueObjectHost(): UniqueObjectHostView {
+        val viewHost = UniqueObjectHostView(context)
+        viewHost.addOnAttachStateChangeListener(
+            object : View.OnAttachStateChangeListener {
+                override fun onViewAttachedToWindow(p0: View?) {
+                    if (rootOverlay == null) {
+                        rootView = viewHost.viewRootImpl.view
+                        rootOverlay = (rootView!!.overlay as ViewGroupOverlay)
+                    }
+                    viewHost.removeOnAttachStateChangeListener(this)
+                }
+
+                override fun onViewDetachedFromWindow(p0: View?) {}
+            }
+        )
+        return viewHost
+    }
+
+    /**
+     * Updates the location that the view should be in. If it changes, an animation may be triggered
+     * going from the old desired location to the new one.
+     *
+     * @param forceNoAnimation optional parameter telling the system not to animate
+     * @param forceStateUpdate optional parameter telling the system to update transition state
+     * ```
+     *                         even if location did not change
+     * ```
+     */
+    private fun updateDesiredLocation(
+        forceNoAnimation: Boolean = false,
+        forceStateUpdate: Boolean = false
+    ) =
+        traceSection("MediaHierarchyManager#updateDesiredLocation") {
+            val desiredLocation = calculateLocation()
+            if (desiredLocation != this.desiredLocation || forceStateUpdate) {
+                if (this.desiredLocation >= 0 && desiredLocation != this.desiredLocation) {
+                    // Only update previous location when it actually changes
+                    previousLocation = this.desiredLocation
+                } else if (forceStateUpdate) {
+                    val onLockscreen =
+                        (!bypassController.bypassEnabled &&
+                            (statusbarState == StatusBarState.KEYGUARD))
+                    if (
+                        desiredLocation == LOCATION_QS &&
+                            previousLocation == LOCATION_LOCKSCREEN &&
+                            !onLockscreen
+                    ) {
+                        // If media active state changed and the device is now unlocked, update the
+                        // previous location so we animate between the correct hosts
+                        previousLocation = LOCATION_QQS
+                    }
+                }
+                val isNewView = this.desiredLocation == -1
+                this.desiredLocation = desiredLocation
+                // Let's perform a transition
+                val animate =
+                    !forceNoAnimation && shouldAnimateTransition(desiredLocation, previousLocation)
+                val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation)
+                val host = getHost(desiredLocation)
+                val willFade = calculateTransformationType() == TRANSFORMATION_TYPE_FADE
+                if (!willFade || isCurrentlyInGuidedTransformation() || !animate) {
+                    // if we're fading, we want the desired location / measurement only to change
+                    // once fully faded. This is happening in the host attachment
+                    mediaCarouselController.onDesiredLocationChanged(
+                        desiredLocation,
+                        host,
+                        animate,
+                        animDuration,
+                        delay
+                    )
+                }
+                performTransitionToNewLocation(isNewView, animate)
+            }
+        }
+
+    private fun performTransitionToNewLocation(isNewView: Boolean, animate: Boolean) =
+        traceSection("MediaHierarchyManager#performTransitionToNewLocation") {
+            if (previousLocation < 0 || isNewView) {
+                cancelAnimationAndApplyDesiredState()
+                return
+            }
+            val currentHost = getHost(desiredLocation)
+            val previousHost = getHost(previousLocation)
+            if (currentHost == null || previousHost == null) {
+                cancelAnimationAndApplyDesiredState()
+                return
+            }
+            updateTargetState()
+            if (isCurrentlyInGuidedTransformation()) {
+                applyTargetStateIfNotAnimating()
+            } else if (animate) {
+                val wasCrossFading = isCrossFadeAnimatorRunning
+                val previewsCrossFadeProgress = animationCrossFadeProgress
+                animator.cancel()
+                if (
+                    currentAttachmentLocation != previousLocation ||
+                        !previousHost.hostView.isAttachedToWindow
+                ) {
+                    // Let's animate to the new position, starting from the current position
+                    // We also go in here in case the view was detached, since the bounds wouldn't
+                    // be correct anymore
+                    animationStartBounds.set(currentBounds)
+                    animationStartClipping.set(currentClipping)
+                } else {
+                    // otherwise, let's take the freshest state, since the current one could
+                    // be outdated
+                    animationStartBounds.set(previousHost.currentBounds)
+                    animationStartClipping.set(previousHost.currentClipping)
+                }
+                val transformationType = calculateTransformationType()
+                var needsCrossFade = transformationType == TRANSFORMATION_TYPE_FADE
+                var crossFadeStartProgress = 0.0f
+                // The alpha is only relevant when not cross fading
+                var newCrossFadeStartLocation = previousLocation
+                if (wasCrossFading) {
+                    if (currentAttachmentLocation == crossFadeAnimationEndLocation) {
+                        if (needsCrossFade) {
+                            // We were previously crossFading and we've already reached
+                            // the end view, Let's start crossfading from the same position there
+                            crossFadeStartProgress = 1.0f - previewsCrossFadeProgress
+                        }
+                        // Otherwise let's fade in from the current alpha, but not cross fade
+                    } else {
+                        // We haven't reached the previous location yet, let's still cross fade from
+                        // where we were.
+                        newCrossFadeStartLocation = crossFadeAnimationStartLocation
+                        if (newCrossFadeStartLocation == desiredLocation) {
+                            // we're crossFading back to where we were, let's start at the end
+                            // position
+                            crossFadeStartProgress = 1.0f - previewsCrossFadeProgress
+                        } else {
+                            // Let's start from where we are right now
+                            crossFadeStartProgress = previewsCrossFadeProgress
+                            // We need to force cross fading as we haven't reached the end location
+                            // yet
+                            needsCrossFade = true
+                        }
+                    }
+                } else if (needsCrossFade) {
+                    // let's not flicker and start with the same alpha
+                    crossFadeStartProgress = (1.0f - carouselAlpha) / 2.0f
+                }
+                isCrossFadeAnimatorRunning = needsCrossFade
+                crossFadeAnimationStartLocation = newCrossFadeStartLocation
+                crossFadeAnimationEndLocation = desiredLocation
+                animationStartAlpha = carouselAlpha
+                animationStartCrossFadeProgress = crossFadeStartProgress
+                adjustAnimatorForTransition(desiredLocation, previousLocation)
+                if (!animationPending) {
+                    rootView?.let {
+                        // Let's delay the animation start until we finished laying out
+                        animationPending = true
+                        it.postOnAnimation(startAnimation)
+                    }
+                }
+            } else {
+                cancelAnimationAndApplyDesiredState()
+            }
+        }
+
+    private fun shouldAnimateTransition(
+        @MediaLocation currentLocation: Int,
+        @MediaLocation previousLocation: Int
+    ): Boolean {
+        if (isCurrentlyInGuidedTransformation()) {
+            return false
+        }
+        if (skipQqsOnExpansion) {
+            return false
+        }
+        // This is an invalid transition, and can happen when using the camera gesture from the
+        // lock screen. Disallow.
+        if (
+            previousLocation == LOCATION_LOCKSCREEN &&
+                desiredLocation == LOCATION_QQS &&
+                statusbarState == StatusBarState.SHADE
+        ) {
+            return false
+        }
+
+        if (
+            currentLocation == LOCATION_QQS &&
+                previousLocation == LOCATION_LOCKSCREEN &&
+                (statusBarStateController.leaveOpenOnKeyguardHide() ||
+                    statusbarState == StatusBarState.SHADE_LOCKED)
+        ) {
+            // Usually listening to the isShown is enough to determine this, but there is some
+            // non-trivial reattaching logic happening that will make the view not-shown earlier
+            return true
+        }
+
+        if (
+            statusbarState == StatusBarState.KEYGUARD &&
+                (currentLocation == LOCATION_LOCKSCREEN || previousLocation == LOCATION_LOCKSCREEN)
+        ) {
+            // We're always fading from lockscreen to keyguard in situations where the player
+            // is already fully hidden
+            return false
+        }
+        return mediaFrame.isShownNotFaded || animator.isRunning || animationPending
+    }
+
+    private fun adjustAnimatorForTransition(desiredLocation: Int, previousLocation: Int) {
+        val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation)
+        animator.apply {
+            duration = animDuration
+            startDelay = delay
+        }
+    }
+
+    private fun getAnimationParams(previousLocation: Int, desiredLocation: Int): Pair<Long, Long> {
+        var animDuration = 200L
+        var delay = 0L
+        if (previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QQS) {
+            // Going to the full shade, let's adjust the animation duration
+            if (
+                statusbarState == StatusBarState.SHADE &&
+                    keyguardStateController.isKeyguardFadingAway
+            ) {
+                delay = keyguardStateController.keyguardFadingAwayDelay
+            }
+            animDuration = (StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE / 2f).toLong()
+        } else if (previousLocation == LOCATION_QQS && desiredLocation == LOCATION_LOCKSCREEN) {
+            animDuration = StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR.toLong()
+        }
+        return animDuration to delay
+    }
+
+    private fun applyTargetStateIfNotAnimating() {
+        if (!animator.isRunning) {
+            // Let's immediately apply the target state (which is interpolated) if there is
+            // no animation running. Otherwise the animation update will already update
+            // the location
+            applyState(targetBounds, carouselAlpha, clipBounds = targetClipping)
+        }
+    }
+
+    /** Updates the bounds that the view wants to be in at the end of the animation. */
+    private fun updateTargetState() {
+        var starthost = getHost(previousLocation)
+        var endHost = getHost(desiredLocation)
+        if (
+            isCurrentlyInGuidedTransformation() &&
+                !isCurrentlyFading() &&
+                starthost != null &&
+                endHost != null
+        ) {
+            val progress = getTransformationProgress()
+            // If either of the hosts are invisible, let's keep them at the other host location to
+            // have a nicer disappear animation. Otherwise the currentBounds of the state might
+            // be undefined
+            if (!endHost.visible) {
+                endHost = starthost
+            } else if (!starthost.visible) {
+                starthost = endHost
+            }
+            val newBounds = endHost.currentBounds
+            val previousBounds = starthost.currentBounds
+            targetBounds = interpolateBounds(previousBounds, newBounds, progress)
+            targetClipping = endHost.currentClipping
+        } else if (endHost != null) {
+            val bounds = endHost.currentBounds
+            targetBounds.set(bounds)
+            targetClipping = endHost.currentClipping
+        }
+    }
+
+    private fun interpolateBounds(
+        startBounds: Rect,
+        endBounds: Rect,
+        progress: Float,
+        result: Rect? = null
+    ): Rect {
+        val left =
+            MathUtils.lerp(startBounds.left.toFloat(), endBounds.left.toFloat(), progress).toInt()
+        val top =
+            MathUtils.lerp(startBounds.top.toFloat(), endBounds.top.toFloat(), progress).toInt()
+        val right =
+            MathUtils.lerp(startBounds.right.toFloat(), endBounds.right.toFloat(), progress).toInt()
+        val bottom =
+            MathUtils.lerp(startBounds.bottom.toFloat(), endBounds.bottom.toFloat(), progress)
+                .toInt()
+        val resultBounds = result ?: Rect()
+        resultBounds.set(left, top, right, bottom)
+        return resultBounds
+    }
+
+    /** @return true if this transformation is guided by an external progress like a finger */
+    fun isCurrentlyInGuidedTransformation(): Boolean {
+        return hasValidStartAndEndLocations() &&
+            getTransformationProgress() >= 0 &&
+            areGuidedTransitionHostsVisible()
+    }
+
+    private fun hasValidStartAndEndLocations(): Boolean {
+        return previousLocation != -1 && desiredLocation != -1
+    }
+
+    /** Calculate the transformation type for the current animation */
+    @VisibleForTesting
+    @TransformationType
+    fun calculateTransformationType(): Int {
+        if (isTransitioningToFullShade) {
+            if (inSplitShade && areGuidedTransitionHostsVisible()) {
+                return TRANSFORMATION_TYPE_TRANSITION
+            }
+            return TRANSFORMATION_TYPE_FADE
+        }
+        if (
+            previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QS ||
+                previousLocation == LOCATION_QS && desiredLocation == LOCATION_LOCKSCREEN
+        ) {
+            // animating between ls and qs should fade, as QS is clipped.
+            return TRANSFORMATION_TYPE_FADE
+        }
+        if (previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QQS) {
+            // animating between ls and qqs should fade when dragging down via e.g. expand button
+            return TRANSFORMATION_TYPE_FADE
+        }
+        return TRANSFORMATION_TYPE_TRANSITION
+    }
+
+    private fun areGuidedTransitionHostsVisible(): Boolean {
+        return getHost(previousLocation)?.visible == true &&
+            getHost(desiredLocation)?.visible == true
+    }
+
+    /**
+     * @return the current transformation progress if we're in a guided transformation and -1
+     * otherwise
+     */
+    private fun getTransformationProgress(): Float {
+        if (skipQqsOnExpansion) {
+            return -1.0f
+        }
+        val progress = getQSTransformationProgress()
+        if (statusbarState != StatusBarState.KEYGUARD && progress >= 0) {
+            return progress
+        }
+        if (isTransitioningToFullShade) {
+            return fullShadeTransitionProgress
+        }
+        return -1.0f
+    }
+
+    private fun getQSTransformationProgress(): Float {
+        val currentHost = getHost(desiredLocation)
+        val previousHost = getHost(previousLocation)
+        if (hasActiveMedia && (currentHost?.location == LOCATION_QS && !inSplitShade)) {
+            if (previousHost?.location == LOCATION_QQS) {
+                if (previousHost.visible || statusbarState != StatusBarState.KEYGUARD) {
+                    return qsExpansion
+                }
+            }
+        }
+        return -1.0f
+    }
+
+    private fun getHost(@MediaLocation location: Int): MediaHost? {
+        if (location < 0) {
+            return null
+        }
+        return mediaHosts[location]
+    }
+
+    private fun cancelAnimationAndApplyDesiredState() {
+        animator.cancel()
+        getHost(desiredLocation)?.let {
+            applyState(it.currentBounds, alpha = 1.0f, immediately = true)
+        }
+    }
+
+    /** Apply the current state to the view, updating it's bounds and desired state */
+    private fun applyState(
+        bounds: Rect,
+        alpha: Float,
+        immediately: Boolean = false,
+        clipBounds: Rect = EMPTY_RECT
+    ) =
+        traceSection("MediaHierarchyManager#applyState") {
+            currentBounds.set(bounds)
+            currentClipping = clipBounds
+            carouselAlpha = if (isCurrentlyFading()) alpha else 1.0f
+            val onlyUseEndState = !isCurrentlyInGuidedTransformation() || isCurrentlyFading()
+            val startLocation = if (onlyUseEndState) -1 else previousLocation
+            val progress = if (onlyUseEndState) 1.0f else getTransformationProgress()
+            val endLocation = resolveLocationForFading()
+            mediaCarouselController.setCurrentState(
+                startLocation,
+                endLocation,
+                progress,
+                immediately
+            )
+            updateHostAttachment()
+            if (currentAttachmentLocation == IN_OVERLAY) {
+                // Setting the clipping on the hierarchy of `mediaFrame` does not work
+                if (!currentClipping.isEmpty) {
+                    currentBounds.intersect(currentClipping)
+                }
+                mediaFrame.setLeftTopRightBottom(
+                    currentBounds.left,
+                    currentBounds.top,
+                    currentBounds.right,
+                    currentBounds.bottom
+                )
+            }
+        }
+
+    private fun updateHostAttachment() =
+        traceSection("MediaHierarchyManager#updateHostAttachment") {
+            var newLocation = resolveLocationForFading()
+            var canUseOverlay = !isCurrentlyFading()
+            if (isCrossFadeAnimatorRunning) {
+                if (
+                    getHost(newLocation)?.visible == true &&
+                        getHost(newLocation)?.hostView?.isShown == false &&
+                        newLocation != desiredLocation
+                ) {
+                    // We're crossfading but the view is already hidden. Let's move to the overlay
+                    // instead. This happens when animating to the full shade using a button click.
+                    canUseOverlay = true
+                }
+            }
+            val inOverlay = isTransitionRunning() && rootOverlay != null && canUseOverlay
+            newLocation = if (inOverlay) IN_OVERLAY else newLocation
+            if (currentAttachmentLocation != newLocation) {
+                currentAttachmentLocation = newLocation
+
+                // Remove the carousel from the old host
+                (mediaFrame.parent as ViewGroup?)?.removeView(mediaFrame)
+
+                // Add it to the new one
+                if (inOverlay) {
+                    rootOverlay!!.add(mediaFrame)
+                } else {
+                    val targetHost = getHost(newLocation)!!.hostView
+                    // When adding back to the host, let's make sure to reset the bounds.
+                    // Usually adding the view will trigger a layout that does this automatically,
+                    // but we sometimes suppress this.
+                    targetHost.addView(mediaFrame)
+                    val left = targetHost.paddingLeft
+                    val top = targetHost.paddingTop
+                    mediaFrame.setLeftTopRightBottom(
+                        left,
+                        top,
+                        left + currentBounds.width(),
+                        top + currentBounds.height()
+                    )
+
+                    if (mediaFrame.childCount > 0) {
+                        val child = mediaFrame.getChildAt(0)
+                        if (mediaFrame.height < child.height) {
+                            Log.wtf(
+                                TAG,
+                                "mediaFrame height is too small for child: " +
+                                    "${mediaFrame.height} vs ${child.height}"
+                            )
+                        }
+                    }
+                }
+                if (isCrossFadeAnimatorRunning) {
+                    // When cross-fading with an animation, we only notify the media carousel of the
+                    // location change, once the view is reattached to the new place and not
+                    // immediately
+                    // when the desired location changes. This callback will update the measurement
+                    // of the carousel, only once we've faded out at the old location and then
+                    // reattach
+                    // to fade it in at the new location.
+                    mediaCarouselController.onDesiredLocationChanged(
+                        newLocation,
+                        getHost(newLocation),
+                        animate = false
+                    )
+                }
+            }
+        }
+
+    /**
+     * Calculate the location when cross fading between locations. While fading out, the content
+     * should remain in the previous location, while after the switch it should be at the desired
+     * location.
+     */
+    private fun resolveLocationForFading(): Int {
+        if (isCrossFadeAnimatorRunning) {
+            // When animating between two hosts with a fade, let's keep ourselves in the old
+            // location for the first half, and then switch over to the end location
+            if (animationCrossFadeProgress > 0.5 || previousLocation == -1) {
+                return crossFadeAnimationEndLocation
+            } else {
+                return crossFadeAnimationStartLocation
+            }
+        }
+        return desiredLocation
+    }
+
+    private fun isTransitionRunning(): Boolean {
+        return isCurrentlyInGuidedTransformation() && getTransformationProgress() != 1.0f ||
+            animator.isRunning ||
+            animationPending
+    }
+
+    @MediaLocation
+    private fun calculateLocation(): Int {
+        if (blockLocationChanges) {
+            // Keep the current location until we're allowed to again
+            return desiredLocation
+        }
+        val onLockscreen =
+            (!bypassController.bypassEnabled && (statusbarState == StatusBarState.KEYGUARD))
+        val location =
+            when {
+                dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY
+                (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
+                qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
+                !hasActiveMedia -> LOCATION_QS
+                onLockscreen && isSplitShadeExpanding() -> LOCATION_QS
+                onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
+                onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN
+                else -> LOCATION_QQS
+            }
+        // When we're on lock screen and the player is not active, we should keep it in QS.
+        // Otherwise it will try to animate a transition that doesn't make sense.
+        if (
+            location == LOCATION_LOCKSCREEN &&
+                getHost(location)?.visible != true &&
+                !statusBarStateController.isDozing
+        ) {
+            return LOCATION_QS
+        }
+        if (
+            location == LOCATION_LOCKSCREEN &&
+                desiredLocation == LOCATION_QS &&
+                collapsingShadeFromQS
+        ) {
+            // When collapsing on the lockscreen, we want to remain in QS
+            return LOCATION_QS
+        }
+        if (
+            location != LOCATION_LOCKSCREEN && desiredLocation == LOCATION_LOCKSCREEN && !fullyAwake
+        ) {
+            // When unlocking from dozing / while waking up, the media shouldn't be transitioning
+            // in an animated way. Let's keep it in the lockscreen until we're fully awake and
+            // reattach it without an animation
+            return LOCATION_LOCKSCREEN
+        }
+        if (skipQqsOnExpansion) {
+            // When doing an immediate expand or collapse, we want to keep it in QS.
+            return LOCATION_QS
+        }
+        return location
+    }
+
+    private fun isSplitShadeExpanding(): Boolean {
+        return inSplitShade && isTransitioningToFullShade
+    }
+
+    /** Are we currently transforming to the full shade and already in QQS */
+    private fun isTransformingToFullShadeAndInQQS(): Boolean {
+        if (!isTransitioningToFullShade) {
+            return false
+        }
+        if (inSplitShade) {
+            // Split shade doesn't use QQS.
+            return false
+        }
+        return fullShadeTransitionProgress > 0.5f
+    }
+
+    /** Is the current transformationType fading */
+    private fun isCurrentlyFading(): Boolean {
+        if (isSplitShadeExpanding()) {
+            // Split shade always uses transition instead of fade.
+            return false
+        }
+        if (isTransitioningToFullShade) {
+            return true
+        }
+        return isCrossFadeAnimatorRunning
+    }
+
+    /** Returns true when the media card could be visible to the user if existed. */
+    private fun isVisibleToUser(): Boolean {
+        return isLockScreenVisibleToUser() ||
+            isLockScreenShadeVisibleToUser() ||
+            isHomeScreenShadeVisibleToUser()
+    }
+
+    private fun isLockScreenVisibleToUser(): Boolean {
+        return !statusBarStateController.isDozing &&
+            !keyguardViewController.isBouncerShowing &&
+            statusBarStateController.state == StatusBarState.KEYGUARD &&
+            allowMediaPlayerOnLockScreen &&
+            statusBarStateController.isExpanded &&
+            !qsExpanded
+    }
+
+    private fun isLockScreenShadeVisibleToUser(): Boolean {
+        return !statusBarStateController.isDozing &&
+            !keyguardViewController.isBouncerShowing &&
+            (statusBarStateController.state == StatusBarState.SHADE_LOCKED ||
+                (statusBarStateController.state == StatusBarState.KEYGUARD && qsExpanded))
+    }
+
+    private fun isHomeScreenShadeVisibleToUser(): Boolean {
+        return !statusBarStateController.isDozing &&
+            statusBarStateController.state == StatusBarState.SHADE &&
+            statusBarStateController.isExpanded
+    }
+
+    companion object {
+        /** Attached in expanded quick settings */
+        const val LOCATION_QS = 0
+
+        /** Attached in the collapsed QS */
+        const val LOCATION_QQS = 1
+
+        /** Attached on the lock screen */
+        const val LOCATION_LOCKSCREEN = 2
+
+        /** Attached on the dream overlay */
+        const val LOCATION_DREAM_OVERLAY = 3
+
+        /** Attached at the root of the hierarchy in an overlay */
+        const val IN_OVERLAY = -1000
+
+        /**
+         * The default transformation type where the hosts transform into each other using a direct
+         * transition
+         */
+        const val TRANSFORMATION_TYPE_TRANSITION = 0
+
+        /**
+         * A transformation type where content fades from one place to another instead of
+         * transitioning
+         */
+        const val TRANSFORMATION_TYPE_FADE = 1
+    }
+}
+
+private val EMPTY_RECT = Rect()
+
+@IntDef(
+    prefix = ["TRANSFORMATION_TYPE_"],
+    value =
+        [
+            MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION,
+            MediaHierarchyManager.TRANSFORMATION_TYPE_FADE
+        ]
+)
+@Retention(AnnotationRetention.SOURCE)
+private annotation class TransformationType
+
+@IntDef(
+    prefix = ["LOCATION_"],
+    value =
+        [
+            MediaHierarchyManager.LOCATION_QS,
+            MediaHierarchyManager.LOCATION_QQS,
+            MediaHierarchyManager.LOCATION_LOCKSCREEN,
+            MediaHierarchyManager.LOCATION_DREAM_OVERLAY
+        ]
+)
+@Retention(AnnotationRetention.SOURCE)
+annotation class MediaLocation
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
similarity index 62%
rename from packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
index 8645922..455b7de 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
@@ -1,9 +1,28 @@
-package com.android.systemui.media
+/*
+ * 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.media.controls.ui
 
 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.util.animation.DisappearParameters
 import com.android.systemui.util.animation.MeasurementInput
 import com.android.systemui.util.animation.MeasurementOutput
@@ -11,7 +30,8 @@
 import java.util.Objects
 import javax.inject.Inject
 
-class MediaHost constructor(
+class MediaHost
+constructor(
     private val state: MediaHostStateHolder,
     private val mediaHierarchyManager: MediaHierarchyManager,
     private val mediaDataManager: MediaDataManager,
@@ -26,14 +46,10 @@
 
     private var inited: Boolean = false
 
-    /**
-     * Are we listening to media data changes?
-     */
+    /** Are we listening to media data changes? */
     private var listeningToMediaData = false
 
-    /**
-     * Get the current bounds on the screen. This makes sure the state is fresh and up to date
-     */
+    /** Get the current bounds on the screen. This makes sure the state is fresh and up to date */
     val currentBounds: Rect = Rect()
         get() {
             hostView.getLocationOnScreen(tmpLocationOnScreen)
@@ -62,38 +78,39 @@
      */
     val currentClipping = Rect()
 
-    private val listener = object : MediaDataManager.Listener {
-        override fun onMediaDataLoaded(
-            key: String,
-            oldKey: String?,
-            data: MediaData,
-            immediately: Boolean,
-            receivedSmartspaceCardLatency: Int,
-            isSsReactivated: Boolean
-        ) {
-            if (immediately) {
+    private val listener =
+        object : MediaDataManager.Listener {
+            override fun onMediaDataLoaded(
+                key: String,
+                oldKey: String?,
+                data: MediaData,
+                immediately: Boolean,
+                receivedSmartspaceCardLatency: Int,
+                isSsReactivated: Boolean
+            ) {
+                if (immediately) {
+                    updateViewVisibility()
+                }
+            }
+
+            override fun onSmartspaceMediaDataLoaded(
+                key: String,
+                data: SmartspaceMediaData,
+                shouldPrioritize: Boolean
+            ) {
                 updateViewVisibility()
             }
-        }
 
-        override fun onSmartspaceMediaDataLoaded(
-            key: String,
-            data: SmartspaceMediaData,
-            shouldPrioritize: Boolean
-        ) {
-            updateViewVisibility()
-        }
-
-        override fun onMediaDataRemoved(key: String) {
-            updateViewVisibility()
-        }
-
-        override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
-            if (immediately) {
+            override fun onMediaDataRemoved(key: String) {
                 updateViewVisibility()
             }
+
+            override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
+                if (immediately) {
+                    updateViewVisibility()
+                }
+            }
         }
-    }
 
     fun addVisibilityChangeListener(listener: (Boolean) -> Unit) {
         visibleChangedListeners.add(listener)
@@ -104,12 +121,14 @@
     }
 
     /**
-     * Initialize this MediaObject and create a host view.
-     * All state should already be set on this host before calling this method in order to avoid
-     * unnecessary state changes which lead to remeasurings later on.
+     * Initialize this MediaObject and create a host view. All state should already be set on this
+     * host before calling this method in order to avoid unnecessary state changes which lead to
+     * remeasurings later on.
      *
      * @param location the location this host name has. Used to identify the host during
+     * ```
      *                 transitions.
+     * ```
      */
     fun init(@MediaLocation location: Int) {
         if (inited) {
@@ -122,36 +141,42 @@
         // Listen by default, as the host might not be attached by our clients, until
         // they get a visibility change. We still want to stay up to date in that case!
         setListeningToMediaData(true)
-        hostView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
-            override fun onViewAttachedToWindow(v: View?) {
-                setListeningToMediaData(true)
-                updateViewVisibility()
-            }
+        hostView.addOnAttachStateChangeListener(
+            object : OnAttachStateChangeListener {
+                override fun onViewAttachedToWindow(v: View?) {
+                    setListeningToMediaData(true)
+                    updateViewVisibility()
+                }
 
-            override fun onViewDetachedFromWindow(v: View?) {
-                setListeningToMediaData(false)
+                override fun onViewDetachedFromWindow(v: View?) {
+                    setListeningToMediaData(false)
+                }
             }
-        })
+        )
 
         // Listen to measurement updates and update our state with it
-        hostView.measurementManager = object : UniqueObjectHostView.MeasurementManager {
-            override fun onMeasure(input: MeasurementInput): MeasurementOutput {
-                // Modify the measurement to exactly match the dimensions
-                if (View.MeasureSpec.getMode(input.widthMeasureSpec) == View.MeasureSpec.AT_MOST) {
-                    input.widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(
-                            View.MeasureSpec.getSize(input.widthMeasureSpec),
-                            View.MeasureSpec.EXACTLY)
+        hostView.measurementManager =
+            object : UniqueObjectHostView.MeasurementManager {
+                override fun onMeasure(input: MeasurementInput): MeasurementOutput {
+                    // Modify the measurement to exactly match the dimensions
+                    if (
+                        View.MeasureSpec.getMode(input.widthMeasureSpec) == View.MeasureSpec.AT_MOST
+                    ) {
+                        input.widthMeasureSpec =
+                            View.MeasureSpec.makeMeasureSpec(
+                                View.MeasureSpec.getSize(input.widthMeasureSpec),
+                                View.MeasureSpec.EXACTLY
+                            )
+                    }
+                    // This will trigger a state change that ensures that we now have a state
+                    // available
+                    state.measurementInput = input
+                    return mediaHostStatesManager.updateCarouselDimensions(location, state)
                 }
-                // This will trigger a state change that ensures that we now have a state available
-                state.measurementInput = input
-                return mediaHostStatesManager.updateCarouselDimensions(location, state)
             }
-        }
 
         // Whenever the state changes, let our state manager know
-        state.changedListener = {
-            mediaHostStatesManager.updateHostState(location, state)
-        }
+        state.changedListener = { mediaHostStatesManager.updateHostState(location, state) }
 
         updateViewVisibility()
     }
@@ -172,17 +197,16 @@
      * the visibility has changed
      */
     fun updateViewVisibility() {
-        state.visible = if (showsOnlyActiveMedia) {
-            mediaDataManager.hasActiveMediaOrRecommendation()
-        } else {
-            mediaDataManager.hasAnyMediaOrRecommendation()
-        }
+        state.visible =
+            if (showsOnlyActiveMedia) {
+                mediaDataManager.hasActiveMediaOrRecommendation()
+            } else {
+                mediaDataManager.hasAnyMediaOrRecommendation()
+            }
         val newVisibility = if (visible) View.VISIBLE else View.GONE
         if (newVisibility != hostView.visibility) {
             hostView.visibility = newVisibility
-            visibleChangedListeners.forEach {
-                it.invoke(visible)
-            }
+            visibleChangedListeners.forEach { it.invoke(visible) }
         }
     }
 
@@ -250,14 +274,10 @@
 
         private var lastDisappearHash = disappearParameters.hashCode()
 
-        /**
-         * A listener for all changes. This won't be copied over when invoking [copy]
-         */
+        /** A listener for all changes. This won't be copied over when invoking [copy] */
         var changedListener: (() -> Unit)? = null
 
-        /**
-         * Get a copy of this state. This won't copy any listeners it may have set
-         */
+        /** Get a copy of this state. This won't copy any listeners it may have set */
         override fun copy(): MediaHostState {
             val mediaHostState = MediaHostStateHolder()
             mediaHostState.expansion = expansion
@@ -312,15 +332,13 @@
 }
 
 /**
- * A description of a media host state that describes the behavior whenever the media carousel
- * is hosted. The HostState notifies the media players of changes to their properties, who
- * in turn will create view states from it.
- * When adding a new property to this, make sure to update the listener and notify them
- * about the changes.
- * In case you need to have a different rendering based on the state, you can add a new
- * constraintState to the [MediaViewController]. Otherwise, similar host states will resolve
- * to the same viewstate, a behavior that is described in [CacheKey]. Make sure to only update
- * that key if the underlying view needs to have a different measurement.
+ * A description of a media host state that describes the behavior whenever the media carousel is
+ * hosted. The HostState notifies the media players of changes to their properties, who in turn will
+ * create view states from it. When adding a new property to this, make sure to update the listener
+ * and notify them about the changes. In case you need to have a different rendering based on the
+ * state, you can add a new constraintState to the [MediaViewController]. Otherwise, similar host
+ * states will resolve to the same viewstate, a behavior that is described in [CacheKey]. Make sure
+ * to only update that key if the underlying view needs to have a different measurement.
  */
 interface MediaHostState {
 
@@ -330,46 +348,36 @@
     }
 
     /**
-     * The last measurement input that this state was measured with. Infers width and height of
-     * the players.
+     * The last measurement input that this state was measured with. Infers width and height of the
+     * players.
      */
     var measurementInput: MeasurementInput?
 
     /**
-     * The expansion of the player, [COLLAPSED] for fully collapsed (up to 3 actions),
-     * [EXPANDED] for fully expanded (up to 5 actions).
+     * The expansion of the player, [COLLAPSED] for fully collapsed (up to 3 actions), [EXPANDED]
+     * for fully expanded (up to 5 actions).
      */
     var expansion: Float
 
-    /**
-     * Fraction of the height animation.
-     */
+    /** Fraction of the height animation. */
     var squishFraction: Float
 
-    /**
-     * Is this host only showing active media or is it showing all of them including resumption?
-     */
+    /** Is this host only showing active media or is it showing all of them including resumption? */
     var showsOnlyActiveMedia: Boolean
 
-    /**
-     * If the view should be VISIBLE or GONE.
-     */
+    /** If the view should be VISIBLE or GONE. */
     val visible: Boolean
 
-    /**
-     * Does this host need any falsing protection?
-     */
+    /** Does this host need any falsing protection? */
     var falsingProtectionNeeded: Boolean
 
     /**
      * The parameters how the view disappears from this location when going to a host that's not
-     * visible. If modified, make sure to set this value again on the host to ensure the values
-     * are propagated
+     * visible. If modified, make sure to set this value again on the host to ensure the values are
+     * propagated
      */
     var disappearParameters: DisappearParameters
 
-    /**
-     * Get a copy of this view state, deepcopying all appropriate members
-     */
+    /** Get a copy of this view state, deepcopying all appropriate members */
     fun copy(): MediaHostState
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt
new file mode 100644
index 0000000..ae3ce33
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2020 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.media.controls.ui
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.util.animation.MeasurementOutput
+import com.android.systemui.util.traceSection
+import javax.inject.Inject
+
+/**
+ * A class responsible for managing all media host states of the various host locations and
+ * coordinating the heights among different players. This class can be used to get the most up to
+ * date state for any location.
+ */
+@SysUISingleton
+class MediaHostStatesManager @Inject constructor() {
+
+    private val callbacks: MutableSet<Callback> = mutableSetOf()
+    private val controllers: MutableSet<MediaViewController> = mutableSetOf()
+
+    /**
+     * The overall sizes of the carousel. This is needed to make sure all players in the carousel
+     * have equal size.
+     */
+    val carouselSizes: MutableMap<Int, MeasurementOutput> = mutableMapOf()
+
+    /** A map with all media states of all locations. */
+    val mediaHostStates: MutableMap<Int, MediaHostState> = mutableMapOf()
+
+    /**
+     * Notify that a media state for a given location has changed. Should only be called from Media
+     * hosts themselves.
+     */
+    fun updateHostState(@MediaLocation location: Int, hostState: MediaHostState) =
+        traceSection("MediaHostStatesManager#updateHostState") {
+            val currentState = mediaHostStates.get(location)
+            if (!hostState.equals(currentState)) {
+                val newState = hostState.copy()
+                mediaHostStates.put(location, newState)
+                updateCarouselDimensions(location, hostState)
+                // First update all the controllers to ensure they get the chance to measure
+                for (controller in controllers) {
+                    controller.stateCallback.onHostStateChanged(location, newState)
+                }
+
+                // Then update all other callbacks which may depend on the controllers above
+                for (callback in callbacks) {
+                    callback.onHostStateChanged(location, newState)
+                }
+            }
+        }
+
+    /**
+     * Get the dimensions of all players combined, which determines the overall height of the media
+     * carousel and the media hosts.
+     */
+    fun updateCarouselDimensions(
+        @MediaLocation location: Int,
+        hostState: MediaHostState
+    ): MeasurementOutput =
+        traceSection("MediaHostStatesManager#updateCarouselDimensions") {
+            val result = MeasurementOutput(0, 0)
+            for (controller in controllers) {
+                val measurement = controller.getMeasurementsForState(hostState)
+                measurement?.let {
+                    if (it.measuredHeight > result.measuredHeight) {
+                        result.measuredHeight = it.measuredHeight
+                    }
+                    if (it.measuredWidth > result.measuredWidth) {
+                        result.measuredWidth = it.measuredWidth
+                    }
+                }
+            }
+            carouselSizes[location] = result
+            return result
+        }
+
+    /** Add a callback to be called when a MediaState has updated */
+    fun addCallback(callback: Callback) {
+        callbacks.add(callback)
+    }
+
+    /** Remove a callback that listens to media states */
+    fun removeCallback(callback: Callback) {
+        callbacks.remove(callback)
+    }
+
+    /**
+     * Register a controller that listens to media states and is used to determine the size of the
+     * media carousel
+     */
+    fun addController(controller: MediaViewController) {
+        controllers.add(controller)
+    }
+
+    /** Notify the manager about the removal of a controller. */
+    fun removeController(controller: MediaViewController) {
+        controllers.remove(controller)
+    }
+
+    interface Callback {
+        /**
+         * Notify the callbacks that a media state for a host has changed, and that the
+         * corresponding view states should be updated and applied
+         */
+        fun onHostStateChanged(@MediaLocation location: Int, mediaHostState: MediaHostState)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaScrollView.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaScrollView.kt
new file mode 100644
index 0000000..0e07465
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaScrollView.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.media.controls.ui
+
+import android.content.Context
+import android.os.SystemClock
+import android.util.AttributeSet
+import android.view.InputDevice
+import android.view.MotionEvent
+import android.view.ViewGroup
+import android.widget.HorizontalScrollView
+import com.android.systemui.Gefingerpoken
+import com.android.wm.shell.animation.physicsAnimator
+
+/**
+ * A ScrollView used in Media that doesn't limit itself to the childs bounds. This is useful when
+ * only measuring children but not the parent, when trying to apply a new scroll position
+ */
+class MediaScrollView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+    HorizontalScrollView(context, attrs, defStyleAttr) {
+
+    lateinit var contentContainer: ViewGroup
+        private set
+    var touchListener: Gefingerpoken? = null
+
+    /**
+     * The target value of the translation X animation. Only valid if the physicsAnimator is running
+     */
+    var animationTargetX = 0.0f
+
+    /**
+     * Get the current content translation. This is usually the normal translationX of the content,
+     * but when animating, it might differ
+     */
+    fun getContentTranslation() =
+        if (contentContainer.physicsAnimator.isRunning()) {
+            animationTargetX
+        } else {
+            contentContainer.translationX
+        }
+
+    /**
+     * Convert between the absolute (left-to-right) and relative (start-to-end) scrollX of the media
+     * carousel. The player indices are always relative (start-to-end) and the scrollView.scrollX is
+     * always absolute. This function is its own inverse.
+     */
+    private fun transformScrollX(scrollX: Int): Int =
+        if (isLayoutRtl) {
+            contentContainer.width - width - scrollX
+        } else {
+            scrollX
+        }
+
+    /** Get the layoutDirection-relative (start-to-end) scroll X position of the carousel. */
+    var relativeScrollX: Int
+        get() = transformScrollX(scrollX)
+        set(value) {
+            scrollX = transformScrollX(value)
+        }
+
+    /** Allow all scrolls to go through, use base implementation */
+    override fun scrollTo(x: Int, y: Int) {
+        if (mScrollX != x || mScrollY != y) {
+            val oldX: Int = mScrollX
+            val oldY: Int = mScrollY
+            mScrollX = x
+            mScrollY = y
+            invalidateParentCaches()
+            onScrollChanged(mScrollX, mScrollY, oldX, oldY)
+            if (!awakenScrollBars()) {
+                postInvalidateOnAnimation()
+            }
+        }
+    }
+
+    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
+        var intercept = false
+        touchListener?.let { intercept = it.onInterceptTouchEvent(ev) }
+        return super.onInterceptTouchEvent(ev) || intercept
+    }
+
+    override fun onTouchEvent(ev: MotionEvent?): Boolean {
+        var touch = false
+        touchListener?.let { touch = it.onTouchEvent(ev) }
+        return super.onTouchEvent(ev) || touch
+    }
+
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        contentContainer = getChildAt(0) as ViewGroup
+    }
+
+    override fun overScrollBy(
+        deltaX: Int,
+        deltaY: Int,
+        scrollX: Int,
+        scrollY: Int,
+        scrollRangeX: Int,
+        scrollRangeY: Int,
+        maxOverScrollX: Int,
+        maxOverScrollY: Int,
+        isTouchEvent: Boolean
+    ): Boolean {
+        if (getContentTranslation() != 0.0f) {
+            // When we're dismissing we ignore all the scrolling
+            return false
+        }
+        return super.overScrollBy(
+            deltaX,
+            deltaY,
+            scrollX,
+            scrollY,
+            scrollRangeX,
+            scrollRangeY,
+            maxOverScrollX,
+            maxOverScrollY,
+            isTouchEvent
+        )
+    }
+
+    /** Cancel the current touch event going on. */
+    fun cancelCurrentScroll() {
+        val now = SystemClock.uptimeMillis()
+        val event = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0)
+        event.source = InputDevice.SOURCE_TOUCHSCREEN
+        super.onTouchEvent(event)
+        event.recycle()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
new file mode 100644
index 0000000..4bf3031
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -0,0 +1,607 @@
+/*
+ * Copyright (C) 2020 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.media.controls.ui
+
+import android.content.Context
+import android.content.res.Configuration
+import androidx.annotation.VisibleForTesting
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.R
+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.CONTROLS_DELAY
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DETAILS_DELAY
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DURATION
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIATITLES_DELAY
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.calculateAlpha
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.animation.MeasurementOutput
+import com.android.systemui.util.animation.TransitionLayout
+import com.android.systemui.util.animation.TransitionLayoutController
+import com.android.systemui.util.animation.TransitionViewState
+import com.android.systemui.util.traceSection
+import javax.inject.Inject
+
+/**
+ * A class responsible for controlling a single instance of a media player handling interactions
+ * with the view instance and keeping the media view states up to date.
+ */
+class MediaViewController
+@Inject
+constructor(
+    private val context: Context,
+    private val configurationController: ConfigurationController,
+    private val mediaHostStatesManager: MediaHostStatesManager,
+    private val logger: MediaViewLogger
+) {
+
+    /**
+     * Indicating that the media view controller is for a notification-based player, session-based
+     * player, or recommendation
+     */
+    enum class TYPE {
+        PLAYER,
+        RECOMMENDATION
+    }
+
+    companion object {
+        @JvmField val GUTS_ANIMATION_DURATION = 500L
+        val controlIds =
+            setOf(
+                R.id.media_progress_bar,
+                R.id.actionNext,
+                R.id.actionPrev,
+                R.id.action0,
+                R.id.action1,
+                R.id.action2,
+                R.id.action3,
+                R.id.action4,
+                R.id.media_scrubbing_elapsed_time,
+                R.id.media_scrubbing_total_time
+            )
+
+        val detailIds =
+            setOf(
+                R.id.header_title,
+                R.id.header_artist,
+                R.id.actionPlayPause,
+            )
+    }
+
+    /** A listener when the current dimensions of the player change */
+    lateinit var sizeChangedListener: () -> Unit
+    private var firstRefresh: Boolean = true
+    @VisibleForTesting private var transitionLayout: TransitionLayout? = null
+    private val layoutController = TransitionLayoutController()
+    private var animationDelay: Long = 0
+    private var animationDuration: Long = 0
+    private var animateNextStateChange: Boolean = false
+    private val measurement = MeasurementOutput(0, 0)
+    private var type: TYPE = TYPE.PLAYER
+
+    /** A map containing all viewStates for all locations of this mediaState */
+    private val viewStates: MutableMap<CacheKey, TransitionViewState?> = mutableMapOf()
+
+    /**
+     * The ending location of the view where it ends when all animations and transitions have
+     * finished
+     */
+    @MediaLocation var currentEndLocation: Int = -1
+
+    /** The starting location of the view where it starts for all animations and transitions */
+    @MediaLocation private var currentStartLocation: Int = -1
+
+    /** The progress of the transition or 1.0 if there is no transition happening */
+    private var currentTransitionProgress: Float = 1.0f
+
+    /** A temporary state used to store intermediate measurements. */
+    private val tmpState = TransitionViewState()
+
+    /** A temporary state used to store intermediate measurements. */
+    private val tmpState2 = TransitionViewState()
+
+    /** A temporary state used to store intermediate measurements. */
+    private val tmpState3 = TransitionViewState()
+
+    /** A temporary cache key to be used to look up cache entries */
+    private val tmpKey = CacheKey()
+
+    /**
+     * The current width of the player. This might not factor in case the player is animating to the
+     * current state, but represents the end state
+     */
+    var currentWidth: Int = 0
+    /**
+     * The current height of the player. This might not factor in case the player is animating to
+     * the current state, but represents the end state
+     */
+    var currentHeight: Int = 0
+
+    /** Get the translationX of the layout */
+    var translationX: Float = 0.0f
+        private set
+        get() {
+            return transitionLayout?.translationX ?: 0.0f
+        }
+
+    /** Get the translationY of the layout */
+    var translationY: Float = 0.0f
+        private set
+        get() {
+            return transitionLayout?.translationY ?: 0.0f
+        }
+
+    /** A callback for RTL config changes */
+    private val configurationListener =
+        object : ConfigurationController.ConfigurationListener {
+            override fun onConfigChanged(newConfig: Configuration?) {
+                // Because the TransitionLayout is not always attached (and calculates/caches layout
+                // results regardless of attach state), we have to force the layoutDirection of the
+                // view
+                // to the correct value for the user's current locale to ensure correct
+                // recalculation
+                // when/after calling refreshState()
+                newConfig?.apply {
+                    if (transitionLayout?.rawLayoutDirection != layoutDirection) {
+                        transitionLayout?.layoutDirection = layoutDirection
+                        refreshState()
+                    }
+                }
+            }
+        }
+
+    /** A callback for media state changes */
+    val stateCallback =
+        object : MediaHostStatesManager.Callback {
+            override fun onHostStateChanged(
+                @MediaLocation location: Int,
+                mediaHostState: MediaHostState
+            ) {
+                if (location == currentEndLocation || location == currentStartLocation) {
+                    setCurrentState(
+                        currentStartLocation,
+                        currentEndLocation,
+                        currentTransitionProgress,
+                        applyImmediately = false
+                    )
+                }
+            }
+        }
+
+    /**
+     * The expanded constraint set used to render a expanded player. If it is modified, make sure to
+     * call [refreshState]
+     */
+    val collapsedLayout = ConstraintSet()
+
+    /**
+     * The expanded constraint set used to render a collapsed player. If it is modified, make sure
+     * to call [refreshState]
+     */
+    val expandedLayout = ConstraintSet()
+
+    /** Whether the guts are visible for the associated player. */
+    var isGutsVisible = false
+        private set
+
+    init {
+        mediaHostStatesManager.addController(this)
+        layoutController.sizeChangedListener = { width: Int, height: Int ->
+            currentWidth = width
+            currentHeight = height
+            sizeChangedListener.invoke()
+        }
+        configurationController.addCallback(configurationListener)
+    }
+
+    /**
+     * Notify this controller that the view has been removed and all listeners should be destroyed
+     */
+    fun onDestroy() {
+        mediaHostStatesManager.removeController(this)
+        configurationController.removeCallback(configurationListener)
+    }
+
+    /** Show guts with an animated transition. */
+    fun openGuts() {
+        if (isGutsVisible) return
+        isGutsVisible = true
+        animatePendingStateChange(GUTS_ANIMATION_DURATION, 0L)
+        setCurrentState(
+            currentStartLocation,
+            currentEndLocation,
+            currentTransitionProgress,
+            applyImmediately = false
+        )
+    }
+
+    /**
+     * Close the guts for the associated player.
+     *
+     * @param immediate if `false`, it will animate the transition.
+     */
+    @JvmOverloads
+    fun closeGuts(immediate: Boolean = false) {
+        if (!isGutsVisible) return
+        isGutsVisible = false
+        if (!immediate) {
+            animatePendingStateChange(GUTS_ANIMATION_DURATION, 0L)
+        }
+        setCurrentState(
+            currentStartLocation,
+            currentEndLocation,
+            currentTransitionProgress,
+            applyImmediately = immediate
+        )
+    }
+
+    private fun ensureAllMeasurements() {
+        val mediaStates = mediaHostStatesManager.mediaHostStates
+        for (entry in mediaStates) {
+            obtainViewState(entry.value)
+        }
+    }
+
+    /** Get the constraintSet for a given expansion */
+    private fun constraintSetForExpansion(expansion: Float): ConstraintSet =
+        if (expansion > 0) expandedLayout else collapsedLayout
+
+    /**
+     * Set the views to be showing/hidden based on the [isGutsVisible] for a given
+     * [TransitionViewState].
+     */
+    private fun setGutsViewState(viewState: TransitionViewState) {
+        val controlsIds =
+            when (type) {
+                TYPE.PLAYER -> MediaViewHolder.controlsIds
+                TYPE.RECOMMENDATION -> RecommendationViewHolder.controlsIds
+            }
+        val gutsIds = GutsViewHolder.ids
+        controlsIds.forEach { id ->
+            viewState.widgetStates.get(id)?.let { state ->
+                // Make sure to use the unmodified state if guts are not visible.
+                state.alpha = if (isGutsVisible) 0f else state.alpha
+                state.gone = if (isGutsVisible) true else state.gone
+            }
+        }
+        gutsIds.forEach { id ->
+            viewState.widgetStates.get(id)?.let { state ->
+                // Make sure to use the unmodified state if guts are visible
+                state.alpha = if (isGutsVisible) state.alpha else 0f
+                state.gone = if (isGutsVisible) state.gone else true
+            }
+        }
+    }
+
+    /** Apply squishFraction to a copy of viewState such that the cached version is untouched. */
+    internal fun squishViewState(
+        viewState: TransitionViewState,
+        squishFraction: Float
+    ): TransitionViewState {
+        val squishedViewState = viewState.copy()
+        squishedViewState.height = (squishedViewState.height * squishFraction).toInt()
+        controlIds.forEach { id ->
+            squishedViewState.widgetStates.get(id)?.let { state ->
+                state.alpha = calculateAlpha(squishFraction, CONTROLS_DELAY, DURATION)
+            }
+        }
+
+        detailIds.forEach { id ->
+            squishedViewState.widgetStates.get(id)?.let { state ->
+                state.alpha = calculateAlpha(squishFraction, DETAILS_DELAY, DURATION)
+            }
+        }
+
+        RecommendationViewHolder.mediaContainersIds.forEach { id ->
+            squishedViewState.widgetStates.get(id)?.let { state ->
+                state.alpha = calculateAlpha(squishFraction, MEDIACONTAINERS_DELAY, DURATION)
+            }
+        }
+
+        RecommendationViewHolder.mediaTitlesAndSubtitlesIds.forEach { id ->
+            squishedViewState.widgetStates.get(id)?.let { state ->
+                state.alpha = calculateAlpha(squishFraction, MEDIATITLES_DELAY, DURATION)
+            }
+        }
+
+        return squishedViewState
+    }
+
+    /**
+     * Obtain a new viewState for a given media state. This usually returns a cached state, but if
+     * it's not available, it will recreate one by measuring, which may be expensive.
+     */
+    @VisibleForTesting
+    fun obtainViewState(state: MediaHostState?): TransitionViewState? {
+        if (state == null || state.measurementInput == null) {
+            return null
+        }
+        // Only a subset of the state is relevant to get a valid viewState. Let's get the cachekey
+        var cacheKey = getKey(state, isGutsVisible, tmpKey)
+        val viewState = viewStates[cacheKey]
+        if (viewState != null) {
+            // we already have cached this measurement, let's continue
+            if (state.squishFraction <= 1f) {
+                return squishViewState(viewState, state.squishFraction)
+            }
+            return viewState
+        }
+        // Copy the key since this might call recursively into it and we're using tmpKey
+        cacheKey = cacheKey.copy()
+        val result: TransitionViewState?
+
+        if (transitionLayout == null) {
+            return null
+        }
+        // Let's create a new measurement
+        if (state.expansion == 0.0f || state.expansion == 1.0f) {
+            result =
+                transitionLayout!!.calculateViewState(
+                    state.measurementInput!!,
+                    constraintSetForExpansion(state.expansion),
+                    TransitionViewState()
+                )
+
+            setGutsViewState(result)
+            // We don't want to cache interpolated or null states as this could quickly fill up
+            // our cache. We only cache the start and the end states since the interpolation
+            // is cheap
+            viewStates[cacheKey] = result
+        } else {
+            // This is an interpolated state
+            val startState = state.copy().also { it.expansion = 0.0f }
+
+            // Given that we have a measurement and a view, let's get (guaranteed) viewstates
+            // from the start and end state and interpolate them
+            val startViewState = obtainViewState(startState) as TransitionViewState
+            val endState = state.copy().also { it.expansion = 1.0f }
+            val endViewState = obtainViewState(endState) as TransitionViewState
+            result =
+                layoutController.getInterpolatedState(startViewState, endViewState, state.expansion)
+        }
+        if (state.squishFraction <= 1f) {
+            return squishViewState(result, state.squishFraction)
+        }
+        return result
+    }
+
+    private fun getKey(state: MediaHostState, guts: Boolean, result: CacheKey): CacheKey {
+        result.apply {
+            heightMeasureSpec = state.measurementInput?.heightMeasureSpec ?: 0
+            widthMeasureSpec = state.measurementInput?.widthMeasureSpec ?: 0
+            expansion = state.expansion
+            gutsVisible = guts
+        }
+        return result
+    }
+
+    /**
+     * Attach a view to this controller. This may perform measurements if it's not available yet and
+     * should therefore be done carefully.
+     */
+    fun attach(transitionLayout: TransitionLayout, type: TYPE) =
+        traceSection("MediaViewController#attach") {
+            updateMediaViewControllerType(type)
+            logger.logMediaLocation("attach $type", currentStartLocation, currentEndLocation)
+            this.transitionLayout = transitionLayout
+            layoutController.attach(transitionLayout)
+            if (currentEndLocation == -1) {
+                return
+            }
+            // Set the previously set state immediately to the view, now that it's finally attached
+            setCurrentState(
+                startLocation = currentStartLocation,
+                endLocation = currentEndLocation,
+                transitionProgress = currentTransitionProgress,
+                applyImmediately = true
+            )
+        }
+
+    /**
+     * Obtain a measurement for a given location. This makes sure that the state is up to date and
+     * all widgets know their location. Calling this method may create a measurement if we don't
+     * have a cached value available already.
+     */
+    fun getMeasurementsForState(hostState: MediaHostState): MeasurementOutput? =
+        traceSection("MediaViewController#getMeasurementsForState") {
+            val viewState = obtainViewState(hostState) ?: return null
+            measurement.measuredWidth = viewState.width
+            measurement.measuredHeight = viewState.height
+            return measurement
+        }
+
+    /**
+     * Set a new state for the controlled view which can be an interpolation between multiple
+     * locations.
+     */
+    fun setCurrentState(
+        @MediaLocation startLocation: Int,
+        @MediaLocation endLocation: Int,
+        transitionProgress: Float,
+        applyImmediately: Boolean
+    ) =
+        traceSection("MediaViewController#setCurrentState") {
+            currentEndLocation = endLocation
+            currentStartLocation = startLocation
+            currentTransitionProgress = transitionProgress
+            logger.logMediaLocation("setCurrentState", startLocation, endLocation)
+
+            val shouldAnimate = animateNextStateChange && !applyImmediately
+
+            val endHostState = mediaHostStatesManager.mediaHostStates[endLocation] ?: return
+            val startHostState = mediaHostStatesManager.mediaHostStates[startLocation]
+
+            // Obtain the view state that we'd want to be at the end
+            // The view might not be bound yet or has never been measured and in that case will be
+            // reset once the state is fully available
+            var endViewState = obtainViewState(endHostState) ?: return
+            endViewState = updateViewStateToCarouselSize(endViewState, endLocation, tmpState2)!!
+            layoutController.setMeasureState(endViewState)
+
+            // If the view isn't bound, we can drop the animation, otherwise we'll execute it
+            animateNextStateChange = false
+            if (transitionLayout == null) {
+                return
+            }
+
+            val result: TransitionViewState
+            var startViewState = obtainViewState(startHostState)
+            startViewState = updateViewStateToCarouselSize(startViewState, startLocation, tmpState3)
+
+            if (!endHostState.visible) {
+                // Let's handle the case where the end is gone first. In this case we take the
+                // start viewState and will make it gone
+                if (startViewState == null || startHostState == null || !startHostState.visible) {
+                    // the start isn't a valid state, let's use the endstate directly
+                    result = endViewState
+                } else {
+                    // Let's get the gone presentation from the start state
+                    result =
+                        layoutController.getGoneState(
+                            startViewState,
+                            startHostState.disappearParameters,
+                            transitionProgress,
+                            tmpState
+                        )
+                }
+            } else if (startHostState != null && !startHostState.visible) {
+                // We have a start state and it is gone.
+                // Let's get presentation from the endState
+                result =
+                    layoutController.getGoneState(
+                        endViewState,
+                        endHostState.disappearParameters,
+                        1.0f - transitionProgress,
+                        tmpState
+                    )
+            } else if (transitionProgress == 1.0f || startViewState == null) {
+                // We're at the end. Let's use that state
+                result = endViewState
+            } else if (transitionProgress == 0.0f) {
+                // We're at the start. Let's use that state
+                result = startViewState
+            } else {
+                result =
+                    layoutController.getInterpolatedState(
+                        startViewState,
+                        endViewState,
+                        transitionProgress,
+                        tmpState
+                    )
+            }
+            logger.logMediaSize("setCurrentState", result.width, result.height)
+            layoutController.setState(
+                result,
+                applyImmediately,
+                shouldAnimate,
+                animationDuration,
+                animationDelay
+            )
+        }
+
+    private fun updateViewStateToCarouselSize(
+        viewState: TransitionViewState?,
+        location: Int,
+        outState: TransitionViewState
+    ): TransitionViewState? {
+        val result = viewState?.copy(outState) ?: return null
+        val overrideSize = mediaHostStatesManager.carouselSizes[location]
+        overrideSize?.let {
+            // To be safe we're using a maximum here. The override size should always be set
+            // properly though.
+            result.height = Math.max(it.measuredHeight, result.height)
+            result.width = Math.max(it.measuredWidth, result.width)
+        }
+        logger.logMediaSize("update to carousel", result.width, result.height)
+        return result
+    }
+
+    private fun updateMediaViewControllerType(type: TYPE) {
+        this.type = type
+
+        // These XML resources contain ConstraintSets that will apply to this player type's layout
+        when (type) {
+            TYPE.PLAYER -> {
+                collapsedLayout.load(context, R.xml.media_session_collapsed)
+                expandedLayout.load(context, R.xml.media_session_expanded)
+            }
+            TYPE.RECOMMENDATION -> {
+                collapsedLayout.load(context, R.xml.media_recommendation_collapsed)
+                expandedLayout.load(context, R.xml.media_recommendation_expanded)
+            }
+        }
+        refreshState()
+    }
+
+    /**
+     * Retrieves the [TransitionViewState] and [MediaHostState] of a [@MediaLocation]. In the event
+     * of [location] not being visible, [locationWhenHidden] will be used instead.
+     *
+     * @param location Target
+     * @param locationWhenHidden Location that will be used when the target is not
+     * [MediaHost.visible]
+     * @return State require for executing a transition, and also the respective [MediaHost].
+     */
+    private fun obtainViewStateForLocation(@MediaLocation location: Int): TransitionViewState? {
+        val mediaHostState = mediaHostStatesManager.mediaHostStates[location] ?: return null
+        return obtainViewState(mediaHostState)
+    }
+
+    /**
+     * Notify that the location is changing right now and a [setCurrentState] change is imminent.
+     * This updates the width the view will me measured with.
+     */
+    fun onLocationPreChange(@MediaLocation newLocation: Int) {
+        obtainViewStateForLocation(newLocation)?.let { layoutController.setMeasureState(it) }
+    }
+
+    /** Request that the next state change should be animated with the given parameters. */
+    fun animatePendingStateChange(duration: Long, delay: Long) {
+        animateNextStateChange = true
+        animationDuration = duration
+        animationDelay = delay
+    }
+
+    /** Clear all existing measurements and refresh the state to match the view. */
+    fun refreshState() =
+        traceSection("MediaViewController#refreshState") {
+            // Let's clear all of our measurements and recreate them!
+            viewStates.clear()
+            if (firstRefresh) {
+                // This is the first bind, let's ensure we pre-cache all measurements. Otherwise
+                // We'll just load these on demand.
+                ensureAllMeasurements()
+                firstRefresh = false
+            }
+            setCurrentState(
+                currentStartLocation,
+                currentEndLocation,
+                currentTransitionProgress,
+                applyImmediately = true
+            )
+        }
+}
+
+/** An internal key for the cache of mediaViewStates. This is a subset of the full host state. */
+private data class CacheKey(
+    var widthMeasureSpec: Int = -1,
+    var heightMeasureSpec: Int = -1,
+    var expansion: Float = 0.0f,
+    var gutsVisible: Boolean = false
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
new file mode 100644
index 0000000..fdac33a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.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.media.controls.ui
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.MediaViewLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import javax.inject.Inject
+
+private const val TAG = "MediaView"
+
+/** A buffered log for media view events that are too noisy for regular logging */
+@SysUISingleton
+class MediaViewLogger @Inject constructor(@MediaViewLog private val buffer: LogBuffer) {
+    fun logMediaSize(reason: String, width: Int, height: Int) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = reason
+                int1 = width
+                int2 = height
+            },
+            { "size ($str1): $int1 x $int2" }
+        )
+    }
+
+    fun logMediaLocation(reason: String, startLocation: Int, endLocation: Int) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = reason
+                int1 = startLocation
+                int2 = endLocation
+            },
+            { "location ($str1): $int1 -> $int2" }
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MetadataAnimationHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MetadataAnimationHandler.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/media/MetadataAnimationHandler.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/MetadataAnimationHandler.kt
index 48f4a16..1cdcf5e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MetadataAnimationHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MetadataAnimationHandler.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
@@ -73,4 +73,4 @@
         exitAnimator.addListener(this)
         enterAnimator.addListener(this)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
similarity index 73%
rename from packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
index 6bc94cd..e9b2cf2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
@@ -1,4 +1,20 @@
-package com.android.systemui.media
+/*
+ * 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.media.controls.ui
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
@@ -23,8 +39,7 @@
 private const val TAG = "Squiggly"
 
 private const val TWO_PI = (Math.PI * 2f).toFloat()
-@VisibleForTesting
-internal const val DISABLED_ALPHA = 77
+@VisibleForTesting internal const val DISABLED_ALPHA = 77
 
 class SquigglyProgress : Drawable() {
 
@@ -86,26 +101,29 @@
                 lastFrameTime = SystemClock.uptimeMillis()
             }
             heightAnimator?.cancel()
-            heightAnimator = ValueAnimator.ofFloat(heightFraction, if (animate) 1f else 0f).apply {
-                if (animate) {
-                    startDelay = 60
-                    duration = 800
-                    interpolator = Interpolators.EMPHASIZED_DECELERATE
-                } else {
-                    duration = 550
-                    interpolator = Interpolators.STANDARD_DECELERATE
-                }
-                addUpdateListener {
-                    heightFraction = it.animatedValue as Float
-                    invalidateSelf()
-                }
-                addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) {
-                        heightAnimator = null
+            heightAnimator =
+                ValueAnimator.ofFloat(heightFraction, if (animate) 1f else 0f).apply {
+                    if (animate) {
+                        startDelay = 60
+                        duration = 800
+                        interpolator = Interpolators.EMPHASIZED_DECELERATE
+                    } else {
+                        duration = 550
+                        interpolator = Interpolators.STANDARD_DECELERATE
                     }
-                })
-                start()
-            }
+                    addUpdateListener {
+                        heightFraction = it.animatedValue as Float
+                        invalidateSelf()
+                    }
+                    addListener(
+                        object : AnimatorListenerAdapter() {
+                            override fun onAnimationEnd(animation: Animator?) {
+                                heightAnimator = null
+                            }
+                        }
+                    )
+                    start()
+                }
         }
 
     override fun draw(canvas: Canvas) {
@@ -120,9 +138,15 @@
         val progress = level / 10_000f
         val totalWidth = bounds.width().toFloat()
         val totalProgressPx = totalWidth * progress
-        val waveProgressPx = totalWidth * (
-            if (!transitionEnabled || progress > matchedWaveEndpoint) progress else
-            lerp(minWaveEndpoint, matchedWaveEndpoint, lerpInv(0f, matchedWaveEndpoint, progress)))
+        val waveProgressPx =
+            totalWidth *
+                (if (!transitionEnabled || progress > matchedWaveEndpoint) progress
+                else
+                    lerp(
+                        minWaveEndpoint,
+                        matchedWaveEndpoint,
+                        lerpInv(0f, matchedWaveEndpoint, progress)
+                    ))
 
         // Build Wiggly Path
         val waveStart = -phaseOffset - waveLength / 2f
@@ -132,10 +156,8 @@
         val computeAmplitude: (Float, Float) -> Float = { x, sign ->
             if (transitionEnabled) {
                 val length = transitionPeriods * waveLength
-                val coeff = lerpInvSat(
-                    waveProgressPx + length / 2f,
-                    waveProgressPx - length / 2f,
-                    x)
+                val coeff =
+                    lerpInvSat(waveProgressPx + length / 2f, waveProgressPx - length / 2f, x)
                 sign * heightFraction * lineAmplitude * coeff
             } else {
                 sign * heightFraction * lineAmplitude
@@ -156,10 +178,7 @@
             val nextX = currentX + dist
             val midX = currentX + dist / 2
             val nextAmp = computeAmplitude(nextX, waveSign)
-            path.cubicTo(
-                midX, currentAmp,
-                midX, nextAmp,
-                nextX, nextAmp)
+            path.cubicTo(midX, currentAmp, midX, nextAmp, nextX, nextAmp)
             currentAmp = nextAmp
             currentX = nextX
         }
@@ -229,7 +248,7 @@
 
     private fun updateColors(tintColor: Int, alpha: Int) {
         wavePaint.color = ColorUtils.setAlphaComponent(tintColor, alpha)
-        linePaint.color = ColorUtils.setAlphaComponent(tintColor,
-                (DISABLED_ALPHA * (alpha / 255f)).toInt())
+        linePaint.color =
+            ColorUtils.setAlphaComponent(tintColor, (DISABLED_ALPHA * (alpha / 255f)).toInt())
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java
rename to packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java
index ed3e109..6caf5c2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media;
+package com.android.systemui.media.controls.util;
 
 import android.annotation.NonNull;
 import android.content.Context;
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataUtils.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/media/MediaDataUtils.java
rename to packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
index b8185b9..bcfceaa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
@@ -14,15 +14,25 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media;
+package com.android.systemui.media.controls.util;
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.text.TextUtils;
 
+/**
+ * Utility class with common methods for media controls
+ */
 public class MediaDataUtils {
 
+    /**
+     * Get the application label for a given package
+     * @param context the context to use
+     * @param packageName Package to check
+     * @param unknownName Fallback string if application is not found
+     * @return The label or fallback string
+     */
     public static String getAppLabel(Context context, String packageName, String unknownName) {
         if (TextUtils.isEmpty(packageName)) {
             return null;
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaFeatureFlag.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFeatureFlag.kt
similarity index 88%
rename from packages/SystemUI/src/com/android/systemui/media/MediaFeatureFlag.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFeatureFlag.kt
index 75eb33d..91dac6f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaFeatureFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFeatureFlag.kt
@@ -14,15 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.util
 
 import android.content.Context
 import com.android.systemui.util.Utils
 import javax.inject.Inject
 
-/**
- * Provides access to the current value of the feature flag.
- */
+/** Provides access to the current value of the feature flag. */
 class MediaFeatureFlag @Inject constructor(private val context: Context) {
     val enabled
         get() = Utils.useQsMediaPlayer(context)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index b85ae48..8d4931a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.util
 
 import android.app.StatusBarManager
 import android.os.UserHandle
@@ -34,9 +34,7 @@
         return enabled || featureFlags.isEnabled(Flags.MEDIA_SESSION_ACTIONS)
     }
 
-    /**
-     * Check whether we support displaying information about mute await connections.
-     */
+    /** Check whether we support displaying information about mute await connections. */
     fun areMuteAwaitConnectionsEnabled() = featureFlags.isEnabled(Flags.MEDIA_MUTE_AWAIT)
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
similarity index 66%
rename from packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
index 0baf01e..3ad8c21 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.util
 
 import com.android.internal.logging.InstanceId
 import com.android.internal.logging.InstanceIdSequence
@@ -22,22 +22,21 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.R
 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 java.lang.IllegalArgumentException
 import javax.inject.Inject
 
 private const val INSTANCE_ID_MAX = 1 shl 20
 
-/**
- * A helper class to log events related to the media controls
- */
+/** A helper class to log events related to the media controls */
 @SysUISingleton
 class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger) {
 
     private val instanceIdSequence = InstanceIdSequence(INSTANCE_ID_MAX)
 
-    /**
-     * Get a new instance ID for a new media control
-     */
+    /** Get a new instance ID for a new media control */
     fun getNewInstanceId(): InstanceId {
         return instanceIdSequence.newInstanceId()
     }
@@ -48,12 +47,13 @@
         instanceId: InstanceId,
         playbackLocation: Int
     ) {
-        val event = when (playbackLocation) {
-            MediaData.PLAYBACK_LOCAL -> MediaUiEvent.LOCAL_MEDIA_ADDED
-            MediaData.PLAYBACK_CAST_LOCAL -> MediaUiEvent.CAST_MEDIA_ADDED
-            MediaData.PLAYBACK_CAST_REMOTE -> MediaUiEvent.REMOTE_MEDIA_ADDED
-            else -> throw IllegalArgumentException("Unknown playback location")
-        }
+        val event =
+            when (playbackLocation) {
+                MediaData.PLAYBACK_LOCAL -> MediaUiEvent.LOCAL_MEDIA_ADDED
+                MediaData.PLAYBACK_CAST_LOCAL -> MediaUiEvent.CAST_MEDIA_ADDED
+                MediaData.PLAYBACK_CAST_REMOTE -> MediaUiEvent.REMOTE_MEDIA_ADDED
+                else -> throw IllegalArgumentException("Unknown playback location")
+            }
         logger.logWithInstanceId(event, uid, packageName, instanceId)
     }
 
@@ -63,12 +63,13 @@
         instanceId: InstanceId,
         playbackLocation: Int
     ) {
-        val event = when (playbackLocation) {
-            MediaData.PLAYBACK_LOCAL -> MediaUiEvent.TRANSFER_TO_LOCAL
-            MediaData.PLAYBACK_CAST_LOCAL -> MediaUiEvent.TRANSFER_TO_CAST
-            MediaData.PLAYBACK_CAST_REMOTE -> MediaUiEvent.TRANSFER_TO_REMOTE
-            else -> throw IllegalArgumentException("Unknown playback location")
-        }
+        val event =
+            when (playbackLocation) {
+                MediaData.PLAYBACK_LOCAL -> MediaUiEvent.TRANSFER_TO_LOCAL
+                MediaData.PLAYBACK_CAST_LOCAL -> MediaUiEvent.TRANSFER_TO_CAST
+                MediaData.PLAYBACK_CAST_REMOTE -> MediaUiEvent.TRANSFER_TO_REMOTE
+                else -> throw IllegalArgumentException("Unknown playback location")
+            }
         logger.logWithInstanceId(event, uid, packageName, instanceId)
     }
 
@@ -107,8 +108,12 @@
     }
 
     fun logLongPressSettings(uid: Int, packageName: String, instanceId: InstanceId) {
-        logger.logWithInstanceId(MediaUiEvent.OPEN_SETTINGS_LONG_PRESS, uid, packageName,
-            instanceId)
+        logger.logWithInstanceId(
+            MediaUiEvent.OPEN_SETTINGS_LONG_PRESS,
+            uid,
+            packageName,
+            instanceId
+        )
     }
 
     fun logCarouselSettings() {
@@ -117,12 +122,13 @@
     }
 
     fun logTapAction(buttonId: Int, uid: Int, packageName: String, instanceId: InstanceId) {
-        val event = when (buttonId) {
-            R.id.actionPlayPause -> MediaUiEvent.TAP_ACTION_PLAY_PAUSE
-            R.id.actionPrev -> MediaUiEvent.TAP_ACTION_PREV
-            R.id.actionNext -> MediaUiEvent.TAP_ACTION_NEXT
-            else -> MediaUiEvent.TAP_ACTION_OTHER
-        }
+        val event =
+            when (buttonId) {
+                R.id.actionPlayPause -> MediaUiEvent.TAP_ACTION_PLAY_PAUSE
+                R.id.actionPrev -> MediaUiEvent.TAP_ACTION_PREV
+                R.id.actionNext -> MediaUiEvent.TAP_ACTION_NEXT
+                else -> MediaUiEvent.TAP_ACTION_OTHER
+            }
 
         logger.logWithInstanceId(event, uid, packageName, instanceId)
     }
@@ -140,148 +146,130 @@
     }
 
     fun logCarouselPosition(@MediaLocation location: Int) {
-        val event = when (location) {
-            MediaHierarchyManager.LOCATION_QQS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QQS
-            MediaHierarchyManager.LOCATION_QS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QS
-            MediaHierarchyManager.LOCATION_LOCKSCREEN ->
-                MediaUiEvent.MEDIA_CAROUSEL_LOCATION_LOCKSCREEN
-            MediaHierarchyManager.LOCATION_DREAM_OVERLAY ->
-                MediaUiEvent.MEDIA_CAROUSEL_LOCATION_DREAM
-            else -> throw IllegalArgumentException("Unknown media carousel location $location")
-        }
+        val event =
+            when (location) {
+                MediaHierarchyManager.LOCATION_QQS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QQS
+                MediaHierarchyManager.LOCATION_QS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QS
+                MediaHierarchyManager.LOCATION_LOCKSCREEN ->
+                    MediaUiEvent.MEDIA_CAROUSEL_LOCATION_LOCKSCREEN
+                MediaHierarchyManager.LOCATION_DREAM_OVERLAY ->
+                    MediaUiEvent.MEDIA_CAROUSEL_LOCATION_DREAM
+                else -> throw IllegalArgumentException("Unknown media carousel location $location")
+            }
         logger.log(event)
     }
 
     fun logRecommendationAdded(packageName: String, instanceId: InstanceId) {
-        logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_ADDED, 0, packageName,
-            instanceId)
+        logger.logWithInstanceId(
+            MediaUiEvent.MEDIA_RECOMMENDATION_ADDED,
+            0,
+            packageName,
+            instanceId
+        )
     }
 
     fun logRecommendationRemoved(packageName: String, instanceId: InstanceId) {
-        logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_REMOVED, 0, packageName,
-            instanceId)
+        logger.logWithInstanceId(
+            MediaUiEvent.MEDIA_RECOMMENDATION_REMOVED,
+            0,
+            packageName,
+            instanceId
+        )
     }
 
     fun logRecommendationActivated(uid: Int, packageName: String, instanceId: InstanceId) {
-        logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_ACTIVATED, uid, packageName,
-            instanceId)
+        logger.logWithInstanceId(
+            MediaUiEvent.MEDIA_RECOMMENDATION_ACTIVATED,
+            uid,
+            packageName,
+            instanceId
+        )
     }
 
     fun logRecommendationItemTap(packageName: String, instanceId: InstanceId, position: Int) {
-        logger.logWithInstanceIdAndPosition(MediaUiEvent.MEDIA_RECOMMENDATION_ITEM_TAP, 0,
-            packageName, instanceId, position)
+        logger.logWithInstanceIdAndPosition(
+            MediaUiEvent.MEDIA_RECOMMENDATION_ITEM_TAP,
+            0,
+            packageName,
+            instanceId,
+            position
+        )
     }
 
     fun logRecommendationCardTap(packageName: String, instanceId: InstanceId) {
-        logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_CARD_TAP, 0, packageName,
-            instanceId)
+        logger.logWithInstanceId(
+            MediaUiEvent.MEDIA_RECOMMENDATION_CARD_TAP,
+            0,
+            packageName,
+            instanceId
+        )
     }
 
     fun logOpenBroadcastDialog(uid: Int, packageName: String, instanceId: InstanceId) {
-        logger.logWithInstanceId(MediaUiEvent.MEDIA_OPEN_BROADCAST_DIALOG, uid, packageName,
-            instanceId)
+        logger.logWithInstanceId(
+            MediaUiEvent.MEDIA_OPEN_BROADCAST_DIALOG,
+            uid,
+            packageName,
+            instanceId
+        )
     }
 }
 
 enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
     @UiEvent(doc = "A new media control was added for media playing locally on the device")
     LOCAL_MEDIA_ADDED(1029),
-
     @UiEvent(doc = "A new media control was added for media cast from the device")
     CAST_MEDIA_ADDED(1030),
-
     @UiEvent(doc = "A new media control was added for media playing remotely")
     REMOTE_MEDIA_ADDED(1031),
-
     @UiEvent(doc = "The media for an existing control was transferred to local playback")
     TRANSFER_TO_LOCAL(1032),
-
     @UiEvent(doc = "The media for an existing control was transferred to a cast device")
     TRANSFER_TO_CAST(1033),
-
     @UiEvent(doc = "The media for an existing control was transferred to a remote device")
     TRANSFER_TO_REMOTE(1034),
-
-    @UiEvent(doc = "A new resumable media control was added")
-    RESUME_MEDIA_ADDED(1013),
-
+    @UiEvent(doc = "A new resumable media control was added") RESUME_MEDIA_ADDED(1013),
     @UiEvent(doc = "An existing active media control was converted into resumable media")
     ACTIVE_TO_RESUME(1014),
-
-    @UiEvent(doc = "A media control timed out")
-    MEDIA_TIMEOUT(1015),
-
-    @UiEvent(doc = "A media control was removed from the carousel")
-    MEDIA_REMOVED(1016),
-
-    @UiEvent(doc = "User swiped to another control within the media carousel")
-    CAROUSEL_PAGE(1017),
-
-    @UiEvent(doc = "The user swiped away the media carousel")
-    DISMISS_SWIPE(1018),
-
-    @UiEvent(doc = "The user long pressed on a media control")
-    OPEN_LONG_PRESS(1019),
-
+    @UiEvent(doc = "A media control timed out") MEDIA_TIMEOUT(1015),
+    @UiEvent(doc = "A media control was removed from the carousel") MEDIA_REMOVED(1016),
+    @UiEvent(doc = "User swiped to another control within the media carousel") CAROUSEL_PAGE(1017),
+    @UiEvent(doc = "The user swiped away the media carousel") DISMISS_SWIPE(1018),
+    @UiEvent(doc = "The user long pressed on a media control") OPEN_LONG_PRESS(1019),
     @UiEvent(doc = "The user dismissed a media control via its long press menu")
     DISMISS_LONG_PRESS(1020),
-
     @UiEvent(doc = "The user opened media settings from a media control's long press menu")
     OPEN_SETTINGS_LONG_PRESS(1021),
-
     @UiEvent(doc = "The user opened media settings from the media carousel")
     OPEN_SETTINGS_CAROUSEL(1022),
-
     @UiEvent(doc = "The play/pause button on a media control was tapped")
     TAP_ACTION_PLAY_PAUSE(1023),
-
-    @UiEvent(doc = "The previous button on a media control was tapped")
-    TAP_ACTION_PREV(1024),
-
-    @UiEvent(doc = "The next button on a media control was tapped")
-    TAP_ACTION_NEXT(1025),
-
+    @UiEvent(doc = "The previous button on a media control was tapped") TAP_ACTION_PREV(1024),
+    @UiEvent(doc = "The next button on a media control was tapped") TAP_ACTION_NEXT(1025),
     @UiEvent(doc = "A custom or generic action button on a media control was tapped")
     TAP_ACTION_OTHER(1026),
-
-    @UiEvent(doc = "The user seeked on a media control using the seekbar")
-    ACTION_SEEK(1027),
-
+    @UiEvent(doc = "The user seeked on a media control using the seekbar") ACTION_SEEK(1027),
     @UiEvent(doc = "The user opened the output switcher from a media control")
     OPEN_OUTPUT_SWITCHER(1028),
-
-    @UiEvent(doc = "The user tapped on a media control view")
-    MEDIA_TAP_CONTENT_VIEW(1036),
-
-    @UiEvent(doc = "The media carousel moved to QQS")
-    MEDIA_CAROUSEL_LOCATION_QQS(1037),
-
-    @UiEvent(doc = "THe media carousel moved to QS")
-    MEDIA_CAROUSEL_LOCATION_QS(1038),
-
+    @UiEvent(doc = "The user tapped on a media control view") MEDIA_TAP_CONTENT_VIEW(1036),
+    @UiEvent(doc = "The media carousel moved to QQS") MEDIA_CAROUSEL_LOCATION_QQS(1037),
+    @UiEvent(doc = "THe media carousel moved to QS") MEDIA_CAROUSEL_LOCATION_QS(1038),
     @UiEvent(doc = "The media carousel moved to the lockscreen")
     MEDIA_CAROUSEL_LOCATION_LOCKSCREEN(1039),
-
     @UiEvent(doc = "The media carousel moved to the dream state")
     MEDIA_CAROUSEL_LOCATION_DREAM(1040),
-
     @UiEvent(doc = "A media recommendation card was added to the media carousel")
     MEDIA_RECOMMENDATION_ADDED(1041),
-
     @UiEvent(doc = "A media recommendation card was removed from the media carousel")
     MEDIA_RECOMMENDATION_REMOVED(1042),
-
     @UiEvent(doc = "An existing media control was made active as a recommendation")
     MEDIA_RECOMMENDATION_ACTIVATED(1043),
-
     @UiEvent(doc = "User tapped on an item in a media recommendation card")
     MEDIA_RECOMMENDATION_ITEM_TAP(1044),
-
     @UiEvent(doc = "User tapped on a media recommendation card")
     MEDIA_RECOMMENDATION_CARD_TAP(1045),
-
     @UiEvent(doc = "User opened the broadcast dialog from a media control")
     MEDIA_OPEN_BROADCAST_DIALOG(1079);
 
     override fun getId() = metricId
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/SmallHash.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/SmallHash.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/media/SmallHash.java
rename to packages/SystemUI/src/com/android/systemui/media/controls/util/SmallHash.java
index de7aac6..97483a6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SmallHash.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/SmallHash.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media;
+package com.android.systemui.media.controls.util;
 
 import java.util.Objects;
 
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 66c036c..3e5d337 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -17,24 +17,22 @@
 package com.android.systemui.media.dagger;
 
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.dagger.MediaTttReceiverLogBuffer;
 import com.android.systemui.log.dagger.MediaTttSenderLogBuffer;
-import com.android.systemui.media.MediaDataManager;
-import com.android.systemui.media.MediaFlags;
-import com.android.systemui.media.MediaHierarchyManager;
-import com.android.systemui.media.MediaHost;
-import com.android.systemui.media.MediaHostStatesManager;
+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.util.MediaFlags;
 import com.android.systemui.media.dream.dagger.MediaComplicationComponent;
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
 import com.android.systemui.media.taptotransfer.MediaTttFlags;
 import com.android.systemui.media.taptotransfer.common.MediaTttLogger;
-import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
 import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger;
-import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
 import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger;
+import com.android.systemui.plugins.log.LogBuffer;
 
 import java.util.Optional;
 
@@ -94,30 +92,6 @@
         return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager);
     }
 
-    /** */
-    @Provides
-    @SysUISingleton
-    static Optional<MediaTttChipControllerSender> providesMediaTttChipControllerSender(
-            MediaTttFlags mediaTttFlags,
-            Lazy<MediaTttChipControllerSender> controllerSenderLazy) {
-        if (!mediaTttFlags.isMediaTttEnabled()) {
-            return Optional.empty();
-        }
-        return Optional.of(controllerSenderLazy.get());
-    }
-
-    /** */
-    @Provides
-    @SysUISingleton
-    static Optional<MediaTttChipControllerReceiver> providesMediaTttChipControllerReceiver(
-            MediaTttFlags mediaTttFlags,
-            Lazy<MediaTttChipControllerReceiver> controllerReceiverLazy) {
-        if (!mediaTttFlags.isMediaTttEnabled()) {
-            return Optional.empty();
-        }
-        return Optional.of(controllerReceiverLazy.get());
-    }
-
     @Provides
     @SysUISingleton
     @MediaTttSenderLogger
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 65c5bc7..69b5698 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.MediaHierarchyManager;
-import com.android.systemui.media.MediaHost;
-import com.android.systemui.media.MediaHostState;
+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.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 91e7b49..20e8ae6 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.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.complication.DreamMediaEntryComplication;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.MediaData;
-import com.android.systemui.media.MediaDataManager;
-import com.android.systemui.media.SmartspaceMediaData;
+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 javax.inject.Inject;
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
index ffcc1f7..e260894 100644
--- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
@@ -21,7 +21,7 @@
 import com.android.settingslib.media.LocalMediaManager
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.media.MediaFlags
+import com.android.systemui.media.controls.util.MediaFlags
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
index 78f4e01..5ace3ea 100644
--- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
@@ -1,9 +1,9 @@
 package com.android.systemui.media.muteawait
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.MediaMuteAwaitLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
 import javax.inject.Inject
 
 /** Log messages for [MediaMuteAwaitConnectionManager]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
index 46b2cc14..78408fc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
@@ -1,9 +1,9 @@
 package com.android.systemui.media.nearby
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.NearbyMediaDevicesLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
 import javax.inject.Inject
 
 /** Log messages for [NearbyMediaDevicesManager]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md
index 6379960..b5a0483 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md
@@ -41,3 +41,5 @@
 ## Testing
 If you want to test out the tap-to-transfer chip without using the `@SystemApi`s, you can use adb
 commands instead. Refer to `MediaTttCommandLineHelper` for information about adb commands.
+
+TODO(b/245610654): Update this page once the chipbar migration is complete.
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
index b565f3c..38c971e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
@@ -16,8 +16,8 @@
 
 package com.android.systemui.media.taptotransfer.common
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
 import com.android.systemui.temporarydisplay.TemporaryViewLogger
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index c3de94f..0a60437 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -21,6 +21,8 @@
 import android.graphics.drawable.Drawable
 import com.android.settingslib.Utils
 import com.android.systemui.R
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
 
 /** Utility methods for media tap-to-transfer. */
 class MediaTttUtils {
@@ -31,6 +33,23 @@
         const val WAKE_REASON = "MEDIA_TRANSFER_ACTIVATED"
 
         /**
+         * Returns the information needed to display the icon in [Icon] form.
+         *
+         * See [getIconInfoFromPackageName].
+         */
+        fun getIconFromPackageName(
+            context: Context,
+            appPackageName: String?,
+            logger: MediaTttLogger,
+        ): Icon {
+            val iconInfo = getIconInfoFromPackageName(context, appPackageName, logger)
+            return Icon.Loaded(
+                iconInfo.drawable,
+                ContentDescription.Loaded(iconInfo.contentDescription)
+            )
+        }
+
+        /**
          * Returns the information needed to display the icon.
          *
          * The information will either contain app name and icon of the app playing media, or a
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 1461293..089625c 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
@@ -36,6 +36,7 @@
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.media.taptotransfer.MediaTttFlags
 import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.media.taptotransfer.common.MediaTttUtils
 import com.android.systemui.statusbar.CommandQueue
@@ -52,6 +53,8 @@
  * A controller to display and hide the Media Tap-To-Transfer chip on the **receiving** device.
  *
  * This chip is shown when a user is transferring media to/from a sending device and this device.
+ *
+ * TODO(b/245610654): Re-name this to be MediaTttReceiverCoordinator.
  */
 @SysUISingleton
 class MediaTttChipControllerReceiver @Inject constructor(
@@ -64,6 +67,7 @@
         configurationController: ConfigurationController,
         powerManager: PowerManager,
         @Main private val mainHandler: Handler,
+        private val mediaTttFlags: MediaTttFlags,
         private val uiEventLogger: MediaTttReceiverUiEventLogger,
         private val viewUtil: ViewUtil,
 ) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>(
@@ -118,7 +122,7 @@
         uiEventLogger.logReceiverStateChange(chipState)
 
         if (chipState == ChipStateReceiver.FAR_FROM_SENDER) {
-            removeView(removalReason = ChipStateReceiver.FAR_FROM_SENDER::class.simpleName!!)
+            removeView(removalReason = ChipStateReceiver.FAR_FROM_SENDER.name)
             return
         }
         if (appIcon == null) {
@@ -138,7 +142,9 @@
     }
 
     override fun start() {
-        commandQueue.addCallback(commandQueueCallbacks)
+        if (mediaTttFlags.isMediaTttEnabled()) {
+            commandQueue.addCallback(commandQueueCallbacks)
+        }
     }
 
     override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index aae973d..6e596ee 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -18,14 +18,11 @@
 
 import android.app.StatusBarManager
 import android.content.Context
-import android.media.MediaRoute2Info
 import android.util.Log
-import android.view.View
 import androidx.annotation.StringRes
 import com.android.internal.logging.UiEventLogger
-import com.android.internal.statusbar.IUndoMediaTransferCallback
 import com.android.systemui.R
-import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.common.shared.model.Text
 import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS
 
 /**
@@ -36,6 +33,7 @@
  * @property stringResId the res ID of the string that should be displayed in the chip. Null if the
  *   state should not have the chip be displayed.
  * @property transferStatus the transfer status that the chip state represents.
+ * @property endItem the item that should be displayed in the end section of the chip.
  * @property timeout the amount of time this chip should display on the screen before it times out
  *   and disappears.
  */
@@ -44,6 +42,7 @@
     val uiEvent: UiEventLogger.UiEventEnum,
     @StringRes val stringResId: Int?,
     val transferStatus: TransferStatus,
+    val endItem: SenderEndItem?,
     val timeout: Long = DEFAULT_TIMEOUT_MILLIS
 ) {
     /**
@@ -56,6 +55,7 @@
         MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST,
         R.string.media_move_closer_to_start_cast,
         transferStatus = TransferStatus.NOT_STARTED,
+        endItem = null,
     ),
 
     /**
@@ -69,6 +69,7 @@
         MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST,
         R.string.media_move_closer_to_end_cast,
         transferStatus = TransferStatus.NOT_STARTED,
+        endItem = null,
     ),
 
     /**
@@ -80,6 +81,7 @@
         MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED,
         R.string.media_transfer_playing_different_device,
         transferStatus = TransferStatus.IN_PROGRESS,
+        endItem = SenderEndItem.Loading,
         timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
     ),
 
@@ -92,6 +94,7 @@
         MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
         R.string.media_transfer_playing_this_device,
         transferStatus = TransferStatus.IN_PROGRESS,
+        endItem = SenderEndItem.Loading,
         timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
     ),
 
@@ -103,36 +106,13 @@
         MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED,
         R.string.media_transfer_playing_different_device,
         transferStatus = TransferStatus.SUCCEEDED,
-    ) {
-        override fun undoClickListener(
-            controllerSender: MediaTttChipControllerSender,
-            routeInfo: MediaRoute2Info,
-            undoCallback: IUndoMediaTransferCallback?,
-            uiEventLogger: MediaTttSenderUiEventLogger,
-            falsingManager: FalsingManager,
-        ): View.OnClickListener? {
-            if (undoCallback == null) {
-                return null
-            }
-            return View.OnClickListener {
-                if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener
-
-                uiEventLogger.logUndoClicked(
-                    MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED
-                )
-                undoCallback.onUndoTriggered()
-                // The external service should eventually send us a TransferToThisDeviceTriggered
-                // state, but that may take too long to go through the binder and the user may be
-                // confused ast o why the UI hasn't changed yet. So, we immediately change the UI
-                // here.
-                controllerSender.displayView(
-                    ChipSenderInfo(
-                        TRANSFER_TO_THIS_DEVICE_TRIGGERED, routeInfo, undoCallback
-                    )
-                )
-            }
-        }
-    },
+        endItem = SenderEndItem.UndoButton(
+            uiEventOnClick =
+            MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED,
+            newState =
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED
+        ),
+    ),
 
     /**
      * A state representing that a transfer back to this device has been successfully completed.
@@ -142,36 +122,13 @@
         MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
         R.string.media_transfer_playing_this_device,
         transferStatus = TransferStatus.SUCCEEDED,
-    ) {
-        override fun undoClickListener(
-            controllerSender: MediaTttChipControllerSender,
-            routeInfo: MediaRoute2Info,
-            undoCallback: IUndoMediaTransferCallback?,
-            uiEventLogger: MediaTttSenderUiEventLogger,
-            falsingManager: FalsingManager,
-        ): View.OnClickListener? {
-            if (undoCallback == null) {
-                return null
-            }
-            return View.OnClickListener {
-                if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener
-
-                uiEventLogger.logUndoClicked(
-                    MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED
-                )
-                undoCallback.onUndoTriggered()
-                // The external service should eventually send us a TransferToReceiverTriggered
-                // state, but that may take too long to go through the binder and the user may be
-                // confused as to why the UI hasn't changed yet. So, we immediately change the UI
-                // here.
-                controllerSender.displayView(
-                    ChipSenderInfo(
-                        TRANSFER_TO_RECEIVER_TRIGGERED, routeInfo, undoCallback
-                    )
-                )
-            }
-        }
-    },
+        endItem = SenderEndItem.UndoButton(
+            uiEventOnClick =
+            MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED,
+            newState =
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED
+        ),
+    ),
 
     /** A state representing that a transfer to the receiver device has failed. */
     TRANSFER_TO_RECEIVER_FAILED(
@@ -179,6 +136,7 @@
         MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED,
         R.string.media_transfer_failed,
         transferStatus = TransferStatus.FAILED,
+        endItem = SenderEndItem.Error,
     ),
 
     /** A state representing that a transfer back to this device has failed. */
@@ -187,6 +145,7 @@
         MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED,
         R.string.media_transfer_failed,
         transferStatus = TransferStatus.FAILED,
+        endItem = SenderEndItem.Error,
     ),
 
     /** A state representing that this device is far away from any receiver device. */
@@ -195,37 +154,27 @@
         MediaTttSenderUiEvents.MEDIA_TTT_SENDER_FAR_FROM_RECEIVER,
         stringResId = null,
         transferStatus = TransferStatus.TOO_FAR,
-    );
+        // We shouldn't be displaying the chipbar anyway
+        endItem = null,
+    ) {
+        override fun getChipTextString(context: Context, otherDeviceName: String): Text {
+            // TODO(b/245610654): Better way to handle this.
+            throw IllegalArgumentException("FAR_FROM_RECEIVER should never be displayed, " +
+                "so its string should never be fetched")
+        }
+    };
 
     /**
      * Returns a fully-formed string with the text that the chip should display.
      *
+     * Throws an NPE if [stringResId] is null.
+     *
      * @param otherDeviceName the name of the other device involved in the transfer.
      */
-    fun getChipTextString(context: Context, otherDeviceName: String): String? {
-        if (stringResId == null) {
-            return null
-        }
-        return context.getString(stringResId, otherDeviceName)
+    open fun getChipTextString(context: Context, otherDeviceName: String): Text {
+        return Text.Loaded(context.getString(stringResId!!, otherDeviceName))
     }
 
-    /**
-     * Returns a click listener for the undo button on the chip. Returns null if this chip state
-     * doesn't have an undo button.
-     *
-     * @param controllerSender passed as a parameter in case we want to display a new chip state
-     *   when undo is clicked.
-     * @param undoCallback if present, the callback that should be called when the user clicks the
-     *   undo button. The undo button will only be shown if this is non-null.
-     */
-    open fun undoClickListener(
-        controllerSender: MediaTttChipControllerSender,
-        routeInfo: MediaRoute2Info,
-        undoCallback: IUndoMediaTransferCallback?,
-        uiEventLogger: MediaTttSenderUiEventLogger,
-        falsingManager: FalsingManager,
-    ): View.OnClickListener? = null
-
     companion object {
         /**
          * Returns the sender state enum associated with the given [displayState] from
@@ -251,6 +200,26 @@
     }
 }
 
+/** Represents the item that should be displayed in the end section of the chip. */
+sealed class SenderEndItem {
+    /** A loading icon should be displayed. */
+    object Loading : SenderEndItem()
+
+    /** An error icon should be displayed. */
+    object Error : SenderEndItem()
+
+    /**
+     * An undo button should be displayed.
+     *
+     * @property uiEventOnClick the UI event to log when this button is clicked.
+     * @property newState the state that should immediately be transitioned to.
+     */
+    data class UndoButton(
+        val uiEventOnClick: UiEventLogger.UiEventEnum,
+        @StatusBarManager.MediaTransferSenderState val newState: Int,
+    ) : SenderEndItem()
+}
+
 // Give the Transfer*Triggered states a longer timeout since those states represent an active
 // process and we should keep the user informed about it as long as possible (but don't allow it to
 // continue indefinitely).
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
deleted file mode 100644
index 5d63145..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.taptotransfer.sender
-
-import android.app.StatusBarManager
-import android.content.Context
-import android.graphics.Rect
-import android.media.MediaRoute2Info
-import android.os.PowerManager
-import android.util.Log
-import android.view.Gravity
-import android.view.MotionEvent
-import android.view.View
-import android.view.ViewGroup
-import android.view.WindowManager
-import android.view.accessibility.AccessibilityManager
-import android.widget.TextView
-import com.android.internal.statusbar.IUndoMediaTransferCallback
-import com.android.internal.widget.CachingIconView
-import com.android.systemui.Gefingerpoken
-import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
-import com.android.systemui.animation.ViewHierarchyAnimator
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
-import com.android.systemui.media.taptotransfer.common.MediaTttUtils
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.temporarydisplay.TemporaryDisplayRemovalReason
-import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
-import com.android.systemui.temporarydisplay.TemporaryViewInfo
-import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.view.ViewUtil
-import javax.inject.Inject
-
-/**
- * A controller to display and hide the Media Tap-To-Transfer chip on the **sending** device. This
- * chip is shown when a user is transferring media to/from this device and a receiver device.
- */
-@SysUISingleton
-open class MediaTttChipControllerSender @Inject constructor(
-        private val commandQueue: CommandQueue,
-        context: Context,
-        @MediaTttSenderLogger logger: MediaTttLogger,
-        windowManager: WindowManager,
-        @Main mainExecutor: DelayableExecutor,
-        accessibilityManager: AccessibilityManager,
-        configurationController: ConfigurationController,
-        powerManager: PowerManager,
-        private val uiEventLogger: MediaTttSenderUiEventLogger,
-        private val falsingManager: FalsingManager,
-        private val falsingCollector: FalsingCollector,
-        private val viewUtil: ViewUtil,
-) : TemporaryViewDisplayController<ChipSenderInfo, MediaTttLogger>(
-        context,
-        logger,
-        windowManager,
-        mainExecutor,
-        accessibilityManager,
-        configurationController,
-        powerManager,
-        R.layout.media_ttt_chip,
-        MediaTttUtils.WINDOW_TITLE,
-        MediaTttUtils.WAKE_REASON,
-) {
-
-    private lateinit var parent: MediaTttChipRootView
-
-    override val windowLayoutParams = commonWindowLayoutParams.apply {
-        gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
-    }
-
-    private val commandQueueCallbacks = object : CommandQueue.Callbacks {
-        override fun updateMediaTapToTransferSenderDisplay(
-                @StatusBarManager.MediaTransferSenderState displayState: Int,
-                routeInfo: MediaRoute2Info,
-                undoCallback: IUndoMediaTransferCallback?
-        ) {
-            this@MediaTttChipControllerSender.updateMediaTapToTransferSenderDisplay(
-                displayState, routeInfo, undoCallback
-            )
-        }
-    }
-
-    private fun updateMediaTapToTransferSenderDisplay(
-        @StatusBarManager.MediaTransferSenderState displayState: Int,
-        routeInfo: MediaRoute2Info,
-        undoCallback: IUndoMediaTransferCallback?
-    ) {
-        val chipState: ChipStateSender? = ChipStateSender.getSenderStateFromId(displayState)
-        val stateName = chipState?.name ?: "Invalid"
-        logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName)
-
-        if (chipState == null) {
-            Log.e(SENDER_TAG, "Unhandled MediaTransferSenderState $displayState")
-            return
-        }
-        uiEventLogger.logSenderStateChange(chipState)
-
-        if (chipState == ChipStateSender.FAR_FROM_RECEIVER) {
-            removeView(removalReason = ChipStateSender.FAR_FROM_RECEIVER.name)
-        } else {
-            displayView(ChipSenderInfo(chipState, routeInfo, undoCallback))
-        }
-    }
-
-    override fun start() {
-        commandQueue.addCallback(commandQueueCallbacks)
-    }
-
-    override fun updateView(
-        newInfo: ChipSenderInfo,
-        currentView: ViewGroup
-    ) {
-        val chipState = newInfo.state
-
-        // Detect falsing touches on the chip.
-        parent = currentView.requireViewById(R.id.media_ttt_sender_chip)
-        parent.touchHandler = object : Gefingerpoken {
-            override fun onTouchEvent(ev: MotionEvent?): Boolean {
-                falsingCollector.onTouchEvent(ev)
-                return false
-            }
-        }
-
-        // App icon
-        val iconInfo = MediaTttUtils.getIconInfoFromPackageName(
-            context, newInfo.routeInfo.clientPackageName, logger
-        )
-        val iconView = currentView.requireViewById<CachingIconView>(R.id.app_icon)
-        iconView.setImageDrawable(iconInfo.drawable)
-        iconView.contentDescription = iconInfo.contentDescription
-
-        // Text
-        val otherDeviceName = newInfo.routeInfo.name.toString()
-        val chipText = chipState.getChipTextString(context, otherDeviceName)
-        currentView.requireViewById<TextView>(R.id.text).text = chipText
-
-        // Loading
-        currentView.requireViewById<View>(R.id.loading).visibility =
-            (chipState.transferStatus == TransferStatus.IN_PROGRESS).visibleIfTrue()
-
-        // Undo
-        val undoView = currentView.requireViewById<View>(R.id.undo)
-        val undoClickListener = chipState.undoClickListener(
-                this,
-                newInfo.routeInfo,
-                newInfo.undoCallback,
-                uiEventLogger,
-                falsingManager,
-        )
-        undoView.setOnClickListener(undoClickListener)
-        undoView.visibility = (undoClickListener != null).visibleIfTrue()
-
-        // Failure
-        currentView.requireViewById<View>(R.id.failure_icon).visibility =
-            (chipState.transferStatus == TransferStatus.FAILED).visibleIfTrue()
-
-        // For accessibility
-        currentView.requireViewById<ViewGroup>(
-                R.id.media_ttt_sender_chip_inner
-        ).contentDescription = "${iconInfo.contentDescription} $chipText"
-    }
-
-    override fun animateViewIn(view: ViewGroup) {
-        val chipInnerView = view.requireViewById<ViewGroup>(R.id.media_ttt_sender_chip_inner)
-        ViewHierarchyAnimator.animateAddition(
-            chipInnerView,
-            ViewHierarchyAnimator.Hotspot.TOP,
-            Interpolators.EMPHASIZED_DECELERATE,
-            duration = ANIMATION_DURATION,
-            includeMargins = true,
-            includeFadeIn = true,
-            // We can only request focus once the animation finishes.
-            onAnimationEnd = { chipInnerView.requestAccessibilityFocus() },
-        )
-    }
-
-    override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
-        ViewHierarchyAnimator.animateRemoval(
-            view.requireViewById<ViewGroup>(R.id.media_ttt_sender_chip_inner),
-            ViewHierarchyAnimator.Hotspot.TOP,
-            Interpolators.EMPHASIZED_ACCELERATE,
-            ANIMATION_DURATION,
-            onAnimationEnd,
-        )
-        // TODO(b/203800644): Add includeMargins as an option to ViewHierarchyAnimator so that the
-        //   animateChipOut matches the animateChipIn.
-    }
-
-    override fun shouldIgnoreViewRemoval(info: ChipSenderInfo, removalReason: String): Boolean {
-        // Don't remove the chip if we're in progress or succeeded, since the user should still be
-        // able to see the status of the transfer. (But do remove it if it's finally timed out.)
-        val transferStatus = info.state.transferStatus
-        if (
-            (transferStatus == TransferStatus.IN_PROGRESS ||
-                transferStatus == TransferStatus.SUCCEEDED) &&
-            removalReason != TemporaryDisplayRemovalReason.REASON_TIMEOUT
-        ) {
-            logger.logRemovalBypass(
-                removalReason, bypassReason = "transferStatus=${transferStatus.name}"
-            )
-            return true
-        }
-        return false
-    }
-
-    override fun getTouchableRegion(view: View, outRect: Rect) {
-        viewUtil.setRectToViewWindowLocation(view, outRect)
-    }
-
-    private fun Boolean.visibleIfTrue(): Int {
-        return if (this) {
-            View.VISIBLE
-        } else {
-            View.GONE
-        }
-    }
-}
-
-data class ChipSenderInfo(
-    val state: ChipStateSender,
-    val routeInfo: MediaRoute2Info,
-    val undoCallback: IUndoMediaTransferCallback? = null
-) : TemporaryViewInfo {
-    override fun getTimeoutMs() = state.timeout
-}
-
-const val SENDER_TAG = "MediaTapToTransferSender"
-private const val ANIMATION_DURATION = 500L
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
new file mode 100644
index 0000000..edf759d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -0,0 +1,202 @@
+/*
+ * 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.media.taptotransfer.sender
+
+import android.app.StatusBarManager
+import android.content.Context
+import android.media.MediaRoute2Info
+import android.util.Log
+import android.view.View
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.statusbar.IUndoMediaTransferCallback
+import com.android.systemui.CoreStartable
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.media.taptotransfer.MediaTttFlags
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.media.taptotransfer.common.MediaTttUtils
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import com.android.systemui.temporarydisplay.chipbar.ChipbarEndItem
+import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
+import com.android.systemui.temporarydisplay.chipbar.SENDER_TAG
+import javax.inject.Inject
+
+/**
+ * A coordinator for showing/hiding the Media Tap-To-Transfer UI on the **sending** device. This UI
+ * is shown when a user is transferring media to/from this device and a receiver device.
+ */
+@SysUISingleton
+class MediaTttSenderCoordinator
+@Inject
+constructor(
+    private val chipbarCoordinator: ChipbarCoordinator,
+    private val commandQueue: CommandQueue,
+    private val context: Context,
+    @MediaTttSenderLogger private val logger: MediaTttLogger,
+    private val mediaTttFlags: MediaTttFlags,
+    private val uiEventLogger: MediaTttSenderUiEventLogger,
+) : CoreStartable {
+
+    private var displayedState: ChipStateSender? = null
+
+    private val commandQueueCallbacks =
+        object : CommandQueue.Callbacks {
+            override fun updateMediaTapToTransferSenderDisplay(
+                @StatusBarManager.MediaTransferSenderState displayState: Int,
+                routeInfo: MediaRoute2Info,
+                undoCallback: IUndoMediaTransferCallback?
+            ) {
+                this@MediaTttSenderCoordinator.updateMediaTapToTransferSenderDisplay(
+                    displayState,
+                    routeInfo,
+                    undoCallback
+                )
+            }
+        }
+
+    override fun start() {
+        if (mediaTttFlags.isMediaTttEnabled()) {
+            commandQueue.addCallback(commandQueueCallbacks)
+        }
+    }
+
+    private fun updateMediaTapToTransferSenderDisplay(
+        @StatusBarManager.MediaTransferSenderState displayState: Int,
+        routeInfo: MediaRoute2Info,
+        undoCallback: IUndoMediaTransferCallback?
+    ) {
+        val chipState: ChipStateSender? = ChipStateSender.getSenderStateFromId(displayState)
+        val stateName = chipState?.name ?: "Invalid"
+        logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName)
+
+        if (chipState == null) {
+            Log.e(SENDER_TAG, "Unhandled MediaTransferSenderState $displayState")
+            return
+        }
+        uiEventLogger.logSenderStateChange(chipState)
+
+        if (chipState == ChipStateSender.FAR_FROM_RECEIVER) {
+            // Return early if we're not displaying a chip anyway
+            val currentDisplayedState = displayedState ?: return
+
+            val removalReason = ChipStateSender.FAR_FROM_RECEIVER.name
+            if (
+                currentDisplayedState.transferStatus == TransferStatus.IN_PROGRESS ||
+                    currentDisplayedState.transferStatus == TransferStatus.SUCCEEDED
+            ) {
+                // Don't remove the chip if we're in progress or succeeded, since the user should
+                // still be able to see the status of the transfer.
+                logger.logRemovalBypass(
+                    removalReason,
+                    bypassReason = "transferStatus=${currentDisplayedState.transferStatus.name}"
+                )
+                return
+            }
+
+            displayedState = null
+            chipbarCoordinator.removeView(removalReason)
+        } else {
+            displayedState = chipState
+            chipbarCoordinator.displayView(
+                createChipbarInfo(
+                    chipState,
+                    routeInfo,
+                    undoCallback,
+                    context,
+                    logger,
+                )
+            )
+        }
+    }
+
+    /**
+     * Creates an instance of [ChipbarInfo] that can be sent to [ChipbarCoordinator] for display.
+     */
+    private fun createChipbarInfo(
+        chipStateSender: ChipStateSender,
+        routeInfo: MediaRoute2Info,
+        undoCallback: IUndoMediaTransferCallback?,
+        context: Context,
+        logger: MediaTttLogger,
+    ): ChipbarInfo {
+        val packageName = routeInfo.clientPackageName
+        val otherDeviceName = routeInfo.name.toString()
+
+        return ChipbarInfo(
+            // Display the app's icon as the start icon
+            startIcon = MediaTttUtils.getIconFromPackageName(context, packageName, logger),
+            text = chipStateSender.getChipTextString(context, otherDeviceName),
+            endItem =
+                when (chipStateSender.endItem) {
+                    null -> null
+                    is SenderEndItem.Loading -> ChipbarEndItem.Loading
+                    is SenderEndItem.Error -> ChipbarEndItem.Error
+                    is SenderEndItem.UndoButton -> {
+                        if (undoCallback != null) {
+                            getUndoButton(
+                                undoCallback,
+                                chipStateSender.endItem.uiEventOnClick,
+                                chipStateSender.endItem.newState,
+                                routeInfo,
+                            )
+                        } else {
+                            null
+                        }
+                    }
+                },
+            vibrationEffect = chipStateSender.transferStatus.vibrationEffect,
+        )
+    }
+
+    /**
+     * Returns an undo button for the chip.
+     *
+     * When the button is clicked: [undoCallback] will be triggered, [uiEvent] will be logged, and
+     * this coordinator will transition to [newState].
+     */
+    private fun getUndoButton(
+        undoCallback: IUndoMediaTransferCallback,
+        uiEvent: UiEventLogger.UiEventEnum,
+        @StatusBarManager.MediaTransferSenderState newState: Int,
+        routeInfo: MediaRoute2Info,
+    ): ChipbarEndItem.Button {
+        val onClickListener =
+            View.OnClickListener {
+                uiEventLogger.logUndoClicked(uiEvent)
+                undoCallback.onUndoTriggered()
+
+                // The external service should eventually send us a new TransferTriggered state, but
+                // but that may take too long to go through the binder and the user may be confused
+                // as to why the UI hasn't changed yet. So, we immediately change the UI here.
+                updateMediaTapToTransferSenderDisplay(
+                    newState,
+                    routeInfo,
+                    // Since we're force-updating the UI, we don't have any [undoCallback] from the
+                    // external service (and TransferTriggered states don't have undo callbacks
+                    // anyway).
+                    undoCallback = null,
+                )
+            }
+
+        return ChipbarEndItem.Button(
+            Text.Resource(R.string.media_transfer_undo),
+            onClickListener,
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt
index f15720d..b963809 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt
@@ -16,16 +16,36 @@
 
 package com.android.systemui.media.taptotransfer.sender
 
-/** Represents the different possible transfer states that we could be in. */
-enum class TransferStatus {
+import android.os.VibrationEffect
+
+/**
+ * Represents the different possible transfer states that we could be in and the vibration effects
+ * that come with updating transfer states.
+ *
+ * @property vibrationEffect an optional vibration effect when the transfer status is changed.
+ */
+enum class TransferStatus(
+    val vibrationEffect: VibrationEffect? = null,
+) {
     /** The transfer hasn't started yet. */
-    NOT_STARTED,
+    NOT_STARTED(
+        vibrationEffect =
+            VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1.0f, 0)
+                .compose()
+    ),
     /** The transfer is currently ongoing but hasn't completed yet. */
-    IN_PROGRESS,
+    IN_PROGRESS(
+        vibrationEffect =
+            VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 1.0f, 0)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.7f, 70)
+                .compose(),
+    ),
     /** The transfer has completed successfully. */
     SUCCEEDED,
     /** The transfer has completed with a failure. */
-    FAILED,
+    FAILED(vibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)),
     /** The device is too far away to do a transfer. */
     TOO_FAR,
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 6da8c69..c089511 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -679,7 +679,9 @@
     public void onViewAttached() {
         final Display display = mView.getDisplay();
         mView.setComponents(mRecentsOptional);
-        mView.setComponents(mCentralSurfacesOptionalLazy.get().get().getPanelController());
+        if (mCentralSurfacesOptionalLazy.get().isPresent()) {
+            mView.setComponents(mCentralSurfacesOptionalLazy.get().get().getPanelController());
+        }
         mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer);
         mView.setOnVerticalChangedListener(this::onVerticalChanged);
         mView.setOnTouchListener(this::onNavigationTouch);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 9702488..403d276 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -148,6 +148,7 @@
 
     private NavigationBarInflaterView mNavigationInflaterView;
     private Optional<Recents> mRecentsOptional = Optional.empty();
+    @Nullable
     private NotificationPanelViewController mPanelView;
     private RotationContextButton mRotationContextButton;
     private FloatingRotationButton mFloatingRotationButton;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index 6424256..6e927b0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -417,6 +417,7 @@
                 stretchEntryBackIndicator(preThresholdStretchProgress(xTranslation))
             GestureState.INACTIVE ->
                 mView.resetStretch()
+            else -> {}
         }
 
         // set y translation
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
index 1ea9347..03503fd 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.privacy.logging
 
 import android.permission.PermissionGroupUsage
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogMessage
 import com.android.systemui.log.dagger.PrivacyLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogMessage
 import com.android.systemui.privacy.PrivacyDialog
 import com.android.systemui.privacy.PrivacyItem
 import java.text.SimpleDateFormat
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 482a139..bb2b441 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -52,6 +52,7 @@
 import com.android.systemui.R
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.animation.Expandable
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -98,10 +99,10 @@
     fun init()
 
     /**
-     * Show the foreground services dialog. The dialog will be expanded from [viewLaunchedFrom] if
+     * Show the foreground services dialog. The dialog will be expanded from [expandable] if
      * it's not `null`.
      */
-    fun showDialog(viewLaunchedFrom: View?)
+    fun showDialog(expandable: Expandable?)
 
     /** Add a [OnNumberOfPackagesChangedListener]. */
     fun addOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener)
@@ -367,7 +368,7 @@
 
     override fun shouldUpdateFooterVisibility() = dialog == null
 
-    override fun showDialog(viewLaunchedFrom: View?) {
+    override fun showDialog(expandable: Expandable?) {
         synchronized(lock) {
             if (dialog == null) {
 
@@ -403,16 +404,18 @@
                 }
 
                 mainExecutor.execute {
-                    viewLaunchedFrom
-                        ?.let {
-                            dialogLaunchAnimator.showFromView(
-                                dialog, it,
-                                cuj = DialogCuj(
-                                    InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
-                                    INTERACTION_JANK_TAG
-                                )
+                    val controller =
+                        expandable?.dialogLaunchController(
+                            DialogCuj(
+                                InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+                                INTERACTION_JANK_TAG,
                             )
-                        } ?: dialog.show()
+                        )
+                    if (controller != null) {
+                        dialogLaunchAnimator.show(dialog, controller)
+                    } else {
+                        dialog.show()
+                    }
                 }
 
                 backgroundExecutor.execute {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index 9d64781..a9943e8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -32,6 +32,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
 import com.android.systemui.globalactions.GlobalActionsDialogLite
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
@@ -156,7 +157,7 @@
             startSettingsActivity()
         } else if (v === powerMenuLite) {
             uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
-            globalActionsDialog?.showOrHideDialog(false, true, v)
+            globalActionsDialog?.showOrHideDialog(false, true, Expandable.fromView(powerMenuLite))
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
index 7511278e..b1b9dd7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
@@ -29,6 +29,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.systemui.R;
+import com.android.systemui.animation.Expandable;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.qs.dagger.QSScope;
@@ -130,7 +131,7 @@
 
     @Override
     public void onClick(View view) {
-        mFgsManagerController.showDialog(mRootView);
+        mFgsManagerController.showDialog(Expandable.fromView(view));
     }
 
     public void refreshState() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 0fe3d16..20f1a8e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -49,7 +49,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.media.MediaHost;
+import com.android.systemui.media.controls.ui.MediaHost;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.qs.QSContainerController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
index 8544f61..c663aa6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
@@ -1,8 +1,8 @@
 package com.android.systemui.qs
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.QSFragmentDisableLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
 import com.android.systemui.statusbar.DisableFlagsLogger
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index f6db775..abc0ade 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -29,9 +29,9 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.MediaHierarchyManager;
-import com.android.systemui.media.MediaHost;
-import com.android.systemui.media.MediaHostState;
+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.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 2727c83..2a80de0 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.MediaHost;
+import com.android.systemui.media.controls.ui.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/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 67bf300..6c1e956 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -39,6 +39,7 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
+import com.android.systemui.animation.Expandable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.common.shared.model.Icon;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -169,7 +170,7 @@
 
     // TODO(b/242040009): Remove this.
     public void showDeviceMonitoringDialog() {
-        mQSSecurityFooterUtils.showDeviceMonitoringDialog(mContext, mView);
+        mQSSecurityFooterUtils.showDeviceMonitoringDialog(mContext, Expandable.fromView(mView));
     }
 
     public void refreshState() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
index ae6ed20..67bc769 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
@@ -75,6 +75,7 @@
 import com.android.systemui.R;
 import com.android.systemui.animation.DialogCuj;
 import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.animation.Expandable;
 import com.android.systemui.common.shared.model.ContentDescription;
 import com.android.systemui.common.shared.model.Icon;
 import com.android.systemui.dagger.SysUISingleton;
@@ -190,8 +191,9 @@
     }
 
     /** Show the device monitoring dialog. */
-    public void showDeviceMonitoringDialog(Context quickSettingsContext, @Nullable View view) {
-        createDialog(quickSettingsContext, view);
+    public void showDeviceMonitoringDialog(Context quickSettingsContext,
+            @Nullable Expandable expandable) {
+        createDialog(quickSettingsContext, expandable);
     }
 
     /**
@@ -440,7 +442,7 @@
         }
     }
 
-    private void createDialog(Context quickSettingsContext, @Nullable View view) {
+    private void createDialog(Context quickSettingsContext, @Nullable Expandable expandable) {
         mShouldUseSettingsButton.set(false);
         mBgHandler.post(() -> {
             String settingsButtonText = getSettingsButton();
@@ -453,9 +455,12 @@
                         ? settingsButtonText : getNegativeButton(), this);
 
                 mDialog.setView(dialogView);
-                if (view != null && view.isAggregatedVisible()) {
-                    mDialogLaunchAnimator.showFromView(mDialog, view, new DialogCuj(
-                            InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG));
+                DialogLaunchAnimator.Controller controller =
+                        expandable != null ? expandable.dialogLaunchController(new DialogCuj(
+                                InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG))
+                                : null;
+                if (controller != null) {
+                    mDialogLaunchAnimator.show(mDialog, controller);
                 } else {
                     mDialog.show();
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index ac46c85..f37d668 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -34,10 +34,12 @@
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.Dumpable;
+import com.android.systemui.ProtoDumpable;
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.dump.nano.SystemUIProtoDump;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.qs.QSTile;
@@ -48,6 +50,7 @@
 import com.android.systemui.qs.external.TileServiceKey;
 import com.android.systemui.qs.external.TileServiceRequestController;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.nano.QsTileState;
 import com.android.systemui.settings.UserFileManager;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.plugins.PluginManager;
@@ -59,16 +62,20 @@
 import com.android.systemui.util.leak.GarbageMonitor;
 import com.android.systemui.util.settings.SecureSettings;
 
+import org.jetbrains.annotations.NotNull;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 import javax.inject.Provider;
@@ -82,7 +89,7 @@
  * This class also provides the interface for adding/removing/changing tiles.
  */
 @SysUISingleton
-public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, Dumpable {
+public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable {
     private static final String TAG = "QSTileHost";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private static final int MAX_QS_INSTANCE_ID = 1 << 20;
@@ -671,4 +678,15 @@
         mTiles.values().stream().filter(obj -> obj instanceof Dumpable)
                 .forEach(o -> ((Dumpable) o).dump(pw, args));
     }
+
+    @Override
+    public void dumpProto(@NotNull SystemUIProtoDump systemUIProtoDump, @NotNull String[] args) {
+        List<QsTileState> data = mTiles.values().stream()
+                .map(QSTile::getState)
+                .map(TileStateToProtoKt::toProto)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        systemUIProtoDump.tiles = data.toArray(new QsTileState[0]);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index 9739974..6aabe3b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -26,8 +26,8 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaHierarchyManager;
-import com.android.systemui.media.MediaHost;
+import com.android.systemui.media.controls.ui.MediaHierarchyManager;
+import com.android.systemui.media.controls.ui.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/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 84d7e65..27d9da6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -25,6 +25,7 @@
 import android.util.AttributeSet;
 import android.util.Pair;
 import android.view.DisplayCutout;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
@@ -231,6 +232,16 @@
         }
     }
 
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        // If using combined headers, only react to touches inside QuickQSPanel
+        if (!mUseCombinedQSHeader || event.getY() > mHeaderQsPanel.getTop()) {
+            return super.onTouchEvent(event);
+        } else {
+            return false;
+        }
+    }
+
     void updateResources() {
         Resources resources = mContext.getResources();
         boolean largeScreenHeaderActive =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt
new file mode 100644
index 0000000..2c8a5a4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.qs
+
+import android.service.quicksettings.Tile
+import android.text.TextUtils
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.external.CustomTile
+import com.android.systemui.qs.nano.QsTileState
+import com.android.systemui.util.nano.ComponentNameProto
+
+fun QSTile.State.toProto(): QsTileState? {
+    if (TextUtils.isEmpty(spec)) return null
+    val state = QsTileState()
+    if (spec.startsWith(CustomTile.PREFIX)) {
+        val protoComponentName = ComponentNameProto()
+        val tileComponentName = CustomTile.getComponentFromSpec(spec)
+        protoComponentName.packageName = tileComponentName.packageName
+        protoComponentName.className = tileComponentName.className
+        state.componentName = protoComponentName
+    } else {
+        state.spec = spec
+    }
+    state.state =
+        when (this.state) {
+            Tile.STATE_UNAVAILABLE -> QsTileState.UNAVAILABLE
+            Tile.STATE_INACTIVE -> QsTileState.INACTIVE
+            Tile.STATE_ACTIVE -> QsTileState.ACTIVE
+            else -> QsTileState.UNAVAILABLE
+        }
+    label?.let { state.label = it.toString() }
+    secondaryLabel?.let { state.secondaryLabel = it.toString() }
+    if (this is QSTile.BooleanState) {
+        state.booleanState = value
+    }
+    return state
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 4cacbba..5d03da3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -35,6 +35,7 @@
 
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.settings.UserTracker;
@@ -53,6 +54,7 @@
 /**
  * Runs the day-to-day operations of which tiles should be bound and when.
  */
+@SysUISingleton
 public class TileServices extends IQSService.Stub {
     static final int DEFAULT_MAX_BOUND = 3;
     static final int REDUCED_MAX_BOUND = 1;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
index cf9b41c..9ba3501 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
@@ -23,13 +23,11 @@
 import android.content.IntentFilter
 import android.os.UserHandle
 import android.provider.Settings
-import android.view.View
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.logging.UiEventLogger
 import com.android.internal.logging.nano.MetricsProto
 import com.android.internal.util.FrameworkStatsLog
-import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.animation.Expandable
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
@@ -74,37 +72,27 @@
     val deviceMonitoringDialogRequests: Flow<Unit>
 
     /**
-     * Show the device monitoring dialog, expanded from [view].
-     *
-     * Important: [view] must be associated to the same [Context] as the [Quick Settings fragment]
-     * [com.android.systemui.qs.QSFragment].
-     */
-    // TODO(b/230830644): Replace view by Expandable interface.
-    fun showDeviceMonitoringDialog(view: View)
-
-    /**
-     * Show the device monitoring dialog.
+     * Show the device monitoring dialog, expanded from [expandable] if it's not null.
      *
      * Important: [quickSettingsContext] *must* be the [Context] associated to the [Quick Settings
      * fragment][com.android.systemui.qs.QSFragment].
      */
-    // TODO(b/230830644): Replace view by Expandable interface.
-    fun showDeviceMonitoringDialog(quickSettingsContext: Context)
+    fun showDeviceMonitoringDialog(quickSettingsContext: Context, expandable: Expandable?)
 
     /** Show the foreground services dialog. */
-    // TODO(b/230830644): Replace view by Expandable interface.
-    fun showForegroundServicesDialog(view: View)
+    fun showForegroundServicesDialog(expandable: Expandable)
 
     /** Show the power menu dialog. */
-    // TODO(b/230830644): Replace view by Expandable interface.
-    fun showPowerMenuDialog(globalActionsDialogLite: GlobalActionsDialogLite, view: View)
+    fun showPowerMenuDialog(
+        globalActionsDialogLite: GlobalActionsDialogLite,
+        expandable: Expandable,
+    )
 
     /** Show the settings. */
     fun showSettings(expandable: Expandable)
 
     /** Show the user switcher. */
-    // TODO(b/230830644): Replace view by Expandable interface.
-    fun showUserSwitcher(view: View)
+    fun showUserSwitcher(context: Context, expandable: Expandable)
 }
 
 @SysUISingleton
@@ -147,28 +135,32 @@
             null,
         )
 
-    override fun showDeviceMonitoringDialog(view: View) {
-        qsSecurityFooterUtils.showDeviceMonitoringDialog(view.context, view)
-        DevicePolicyEventLogger.createEvent(
-                FrameworkStatsLog.DEVICE_POLICY_EVENT__EVENT_ID__DO_USER_INFO_CLICKED
-            )
-            .write()
+    override fun showDeviceMonitoringDialog(
+        quickSettingsContext: Context,
+        expandable: Expandable?,
+    ) {
+        qsSecurityFooterUtils.showDeviceMonitoringDialog(quickSettingsContext, expandable)
+        if (expandable != null) {
+            DevicePolicyEventLogger.createEvent(
+                    FrameworkStatsLog.DEVICE_POLICY_EVENT__EVENT_ID__DO_USER_INFO_CLICKED
+                )
+                .write()
+        }
     }
 
-    override fun showDeviceMonitoringDialog(quickSettingsContext: Context) {
-        qsSecurityFooterUtils.showDeviceMonitoringDialog(quickSettingsContext, /* view= */ null)
+    override fun showForegroundServicesDialog(expandable: Expandable) {
+        fgsManagerController.showDialog(expandable)
     }
 
-    override fun showForegroundServicesDialog(view: View) {
-        fgsManagerController.showDialog(view)
-    }
-
-    override fun showPowerMenuDialog(globalActionsDialogLite: GlobalActionsDialogLite, view: View) {
+    override fun showPowerMenuDialog(
+        globalActionsDialogLite: GlobalActionsDialogLite,
+        expandable: Expandable,
+    ) {
         uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
         globalActionsDialogLite.showOrHideDialog(
             /* keyguardShowing= */ false,
             /* isDeviceProvisioned= */ true,
-            view,
+            expandable,
         )
     }
 
@@ -189,21 +181,21 @@
         )
     }
 
-    override fun showUserSwitcher(view: View) {
+    override fun showUserSwitcher(context: Context, expandable: Expandable) {
         if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
-            userSwitchDialogController.showDialog(view)
+            userSwitchDialogController.showDialog(context, expandable)
             return
         }
 
         val intent =
-            Intent(view.context, UserSwitcherActivity::class.java).apply {
+            Intent(context, UserSwitcherActivity::class.java).apply {
                 addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
             }
 
         activityStarter.startActivity(
             intent,
             true /* dismissShade */,
-            ActivityLaunchAnimator.Controller.fromView(view, null),
+            expandable.activityLaunchController(),
             true /* showOverlockscreenwhenlocked */,
             UserHandle.SYSTEM,
         )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
index dd1ffcc..3e39c8e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
@@ -31,6 +31,7 @@
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.R
+import com.android.systemui.animation.Expandable
 import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.people.ui.view.PeopleViewBinder.bind
@@ -125,7 +126,7 @@
                 launch {
                     viewModel.security.collect { security ->
                         if (previousSecurity != security) {
-                            bindSecurity(securityHolder, security)
+                            bindSecurity(view.context, securityHolder, security)
                             previousSecurity = security
                         }
                     }
@@ -159,6 +160,7 @@
     }
 
     private fun bindSecurity(
+        quickSettingsContext: Context,
         securityHolder: TextButtonViewHolder,
         security: FooterActionsSecurityButtonViewModel?,
     ) {
@@ -171,9 +173,12 @@
         // Make sure that the chevron is visible and that the button is clickable if there is a
         // listener.
         val chevron = securityHolder.chevron
-        if (security.onClick != null) {
+        val onClick = security.onClick
+        if (onClick != null) {
             securityView.isClickable = true
-            securityView.setOnClickListener(security.onClick)
+            securityView.setOnClickListener {
+                onClick(quickSettingsContext, Expandable.fromView(securityView))
+            }
             chevron.isVisible = true
         } else {
             securityView.isClickable = false
@@ -205,7 +210,9 @@
             foregroundServicesWithNumberView.isVisible = false
 
             foregroundServicesWithTextView.isVisible = true
-            foregroundServicesWithTextView.setOnClickListener(foregroundServices.onClick)
+            foregroundServicesWithTextView.setOnClickListener {
+                foregroundServices.onClick(Expandable.fromView(foregroundServicesWithTextView))
+            }
             foregroundServicesWithTextHolder.text.text = foregroundServices.text
             foregroundServicesWithTextHolder.newDot.isVisible = foregroundServices.hasNewChanges
         } else {
@@ -213,7 +220,9 @@
             foregroundServicesWithTextView.isVisible = false
 
             foregroundServicesWithNumberView.visibility = View.VISIBLE
-            foregroundServicesWithNumberView.setOnClickListener(foregroundServices.onClick)
+            foregroundServicesWithNumberView.setOnClickListener {
+                foregroundServices.onClick(Expandable.fromView(foregroundServicesWithTextView))
+            }
             foregroundServicesWithNumberHolder.number.text = foregroundServicesCount.toString()
             foregroundServicesWithNumberHolder.number.contentDescription = foregroundServices.text
             foregroundServicesWithNumberHolder.newDot.isVisible = foregroundServices.hasNewChanges
@@ -229,7 +238,7 @@
         }
 
         buttonView.setBackgroundResource(model.background)
-        buttonView.setOnClickListener(model.onClick)
+        buttonView.setOnClickListener { model.onClick(Expandable.fromView(buttonView)) }
 
         val icon = model.icon
         val iconView = button.icon
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
index 9b5f683..8d819da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.qs.footer.ui.viewmodel
 
 import android.annotation.DrawableRes
-import android.view.View
+import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
 
 /**
@@ -29,7 +29,5 @@
     val icon: Icon,
     val iconTint: Int?,
     @DrawableRes val background: Int,
-    // TODO(b/230830644): Replace View by an Expandable interface that can expand in either dialog
-    // or activity.
-    val onClick: (View) -> Unit,
+    val onClick: (Expandable) -> Unit,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsForegroundServicesButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsForegroundServicesButtonViewModel.kt
index 98b53cb..ff8130d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsForegroundServicesButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsForegroundServicesButtonViewModel.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.qs.footer.ui.viewmodel
 
-import android.view.View
+import com.android.systemui.animation.Expandable
 
 /** A ViewModel for the foreground services button. */
 data class FooterActionsForegroundServicesButtonViewModel(
@@ -24,5 +24,5 @@
     val text: String,
     val displayText: Boolean,
     val hasNewChanges: Boolean,
-    val onClick: (View) -> Unit,
+    val onClick: (Expandable) -> Unit,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsSecurityButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsSecurityButtonViewModel.kt
index 98ab129..3450505 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsSecurityButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsSecurityButtonViewModel.kt
@@ -16,12 +16,13 @@
 
 package com.android.systemui.qs.footer.ui.viewmodel
 
-import android.view.View
+import android.content.Context
+import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
 
 /** A ViewModel for the security button. */
 data class FooterActionsSecurityButtonViewModel(
     val icon: Icon,
     val text: String,
-    val onClick: ((View) -> Unit)?,
+    val onClick: ((quickSettingsContext: Context, Expandable) -> Unit)?,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index d3c06f6..dee6fad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import android.util.Log
-import android.view.View
 import androidx.lifecycle.DefaultLifecycleObserver
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
@@ -199,50 +198,51 @@
      */
     suspend fun observeDeviceMonitoringDialogRequests(quickSettingsContext: Context) {
         footerActionsInteractor.deviceMonitoringDialogRequests.collect {
-            footerActionsInteractor.showDeviceMonitoringDialog(quickSettingsContext)
+            footerActionsInteractor.showDeviceMonitoringDialog(
+                quickSettingsContext,
+                expandable = null,
+            )
         }
     }
 
-    private fun onSecurityButtonClicked(view: View) {
+    private fun onSecurityButtonClicked(quickSettingsContext: Context, expandable: Expandable) {
         if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
             return
         }
 
-        footerActionsInteractor.showDeviceMonitoringDialog(view)
+        footerActionsInteractor.showDeviceMonitoringDialog(quickSettingsContext, expandable)
     }
 
-    private fun onForegroundServiceButtonClicked(view: View) {
+    private fun onForegroundServiceButtonClicked(expandable: Expandable) {
         if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
             return
         }
 
-        footerActionsInteractor.showForegroundServicesDialog(view)
+        footerActionsInteractor.showForegroundServicesDialog(expandable)
     }
 
-    private fun onUserSwitcherClicked(view: View) {
+    private fun onUserSwitcherClicked(expandable: Expandable) {
         if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
             return
         }
 
-        footerActionsInteractor.showUserSwitcher(view)
+        footerActionsInteractor.showUserSwitcher(context, expandable)
     }
 
-    // TODO(b/230830644): Replace View by an Expandable interface that can expand in either dialog
-    // or activity.
-    private fun onSettingsButtonClicked(view: View) {
+    private fun onSettingsButtonClicked(expandable: Expandable) {
         if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
             return
         }
 
-        footerActionsInteractor.showSettings(Expandable.fromView(view))
+        footerActionsInteractor.showSettings(expandable)
     }
 
-    private fun onPowerButtonClicked(view: View) {
+    private fun onPowerButtonClicked(expandable: Expandable) {
         if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
             return
         }
 
-        footerActionsInteractor.showPowerMenuDialog(globalActionsDialogLite, view)
+        footerActionsInteractor.showPowerMenuDialog(globalActionsDialogLite, expandable)
     }
 
     private fun userSwitcherButton(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index 6038006..931dc8d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -17,12 +17,12 @@
 package com.android.systemui.qs.logging
 
 import android.service.quicksettings.Tile
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.VERBOSE
-import com.android.systemui.log.LogMessage
 import com.android.systemui.log.dagger.QSLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.VERBOSE
+import com.android.systemui.plugins.log.LogMessage
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.statusbar.StatusBarState
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto b/packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto
new file mode 100644
index 0000000..2a61033
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+package com.android.systemui.qs;
+
+import "frameworks/base/packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto";
+
+option java_multiple_files = true;
+
+message QsTileState {
+  oneof identifier {
+    string spec = 1;
+    com.android.systemui.util.ComponentNameProto component_name = 2;
+  }
+
+  enum State {
+    UNAVAILABLE = 0;
+    INACTIVE = 1;
+    ACTIVE = 2;
+  }
+
+  State state = 3;
+  oneof optional_boolean_state {
+    bool boolean_state = 4;
+  }
+  oneof optional_label {
+    string label = 5;
+  }
+  oneof optional_secondary_label {
+    string secondary_label = 6;
+  }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index d2d5063..b6b657e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -26,6 +26,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.logging.MetricsLogger;
@@ -43,6 +44,9 @@
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.user.data.source.UserRecord;
 
+import java.util.List;
+import java.util.stream.Collectors;
+
 import javax.inject.Inject;
 
 /**
@@ -83,6 +87,13 @@
         private final FalsingManager mFalsingManager;
         private @Nullable UserSwitchDialogController.DialogShower mDialogShower;
 
+        @NonNull
+        @Override
+        protected List<UserRecord> getUsers() {
+            return super.getUsers().stream().filter(
+                    userRecord -> !userRecord.isManageUsers).collect(Collectors.toList());
+        }
+
         @Inject
         public Adapter(Context context, UserSwitcherController controller,
                 UiEventLogger uiEventLogger, FalsingManager falsingManager) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index bdcc6b0..314252b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -23,13 +23,13 @@
 import android.content.Intent
 import android.provider.Settings
 import android.view.LayoutInflater
-import android.view.View
 import androidx.annotation.VisibleForTesting
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.R
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.animation.Expandable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
@@ -77,10 +77,10 @@
      * Show a [UserDialog].
      *
      * Populate the dialog with information from and adapter obtained from
-     * [userDetailViewAdapterProvider] and show it as launched from [view].
+     * [userDetailViewAdapterProvider] and show it as launched from [expandable].
      */
-    fun showDialog(view: View) {
-        with(dialogFactory(view.context)) {
+    fun showDialog(context: Context, expandable: Expandable) {
+        with(dialogFactory(context)) {
             setShowForAllUsers(true)
             setCanceledOnTouchOutside(true)
 
@@ -112,13 +112,19 @@
 
             adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid))
 
-            dialogLaunchAnimator.showFromView(
-                this, view,
-                cuj = DialogCuj(
-                    InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
-                    INTERACTION_JANK_TAG
+            val controller =
+                expandable.dialogLaunchController(
+                    DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)
                 )
-            )
+            if (controller != null) {
+                dialogLaunchAnimator.show(
+                    this,
+                    controller,
+                )
+            } else {
+                show()
+            }
+
             uiEventLogger.log(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN)
             adapter.injectDialogShower(DialogShowerImpl(this, dialogLaunchAnimator))
         }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
new file mode 100644
index 0000000..017e57f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.screenshot
+
+import android.content.ClipData
+import android.content.ClipDescription
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import com.android.systemui.R
+
+object ActionIntentCreator {
+    /** @return a chooser intent to share the given URI with the optional provided subject. */
+    fun createShareIntent(uri: Uri, subject: String?): Intent {
+        // Create a share intent, this will always go through the chooser activity first
+        // which should not trigger auto-enter PiP
+        val sharingIntent =
+            Intent(Intent.ACTION_SEND).apply {
+                setDataAndType(uri, "image/png")
+                putExtra(Intent.EXTRA_STREAM, uri)
+
+                // Include URI in ClipData also, so that grantPermission picks it up.
+                // We don't use setData here because some apps interpret this as "to:".
+                clipData =
+                    ClipData(
+                        ClipDescription("content", arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN)),
+                        ClipData.Item(uri)
+                    )
+
+                putExtra(Intent.EXTRA_SUBJECT, subject)
+                addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+            }
+
+        return Intent.createChooser(sharingIntent, null)
+            .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+            .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+    }
+
+    /**
+     * @return an ACTION_EDIT intent for the given URI, directed to config_screenshotEditor if
+     * available.
+     */
+    fun createEditIntent(uri: Uri, context: Context): Intent {
+        val editIntent = Intent(Intent.ACTION_EDIT)
+
+        context.getString(R.string.config_screenshotEditor)?.let {
+            if (it.isNotEmpty()) {
+                editIntent.component = ComponentName.unflattenFromString(it)
+            }
+        }
+
+        return editIntent
+            .setDataAndType(uri, "image/png")
+            .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+            .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+            .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
new file mode 100644
index 0000000..5961635
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -0,0 +1,159 @@
+/*
+ * 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.screenshot
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.RemoteException
+import android.os.UserHandle
+import android.util.Log
+import android.view.Display
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.IRemoteAnimationRunner
+import android.view.RemoteAnimationAdapter
+import android.view.RemoteAnimationTarget
+import android.view.WindowManager
+import android.view.WindowManagerGlobal
+import com.android.internal.infra.ServiceConnector
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+class ActionIntentExecutor
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    private val context: Context,
+) {
+    /**
+     * Execute the given intent with startActivity while performing operations for screenshot action
+     * launching.
+     * - Dismiss the keyguard first
+     * - If the userId is not the current user, proxy to a service running as that user to execute
+     * - After startActivity, optionally override the pending app transition.
+     */
+    fun launchIntentAsync(
+        intent: Intent,
+        bundle: Bundle,
+        userId: Int,
+        overrideTransition: Boolean,
+    ) {
+        applicationScope.launch { launchIntent(intent, bundle, userId, overrideTransition) }
+    }
+
+    suspend fun launchIntent(
+        intent: Intent,
+        bundle: Bundle,
+        userId: Int,
+        overrideTransition: Boolean,
+    ) {
+        withContext(bgDispatcher) {
+            dismissKeyguard()
+
+            if (userId == UserHandle.myUserId()) {
+                context.startActivity(intent, bundle)
+            } else {
+                launchCrossProfileIntent(userId, intent, bundle)
+            }
+
+            if (overrideTransition) {
+                val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0)
+                try {
+                    WindowManagerGlobal.getWindowManagerService()
+                        .overridePendingAppTransitionRemote(runner, Display.DEFAULT_DISPLAY)
+                } catch (e: Exception) {
+                    Log.e(TAG, "Error overriding screenshot app transition", e)
+                }
+            }
+        }
+    }
+
+    private val proxyConnector: ServiceConnector<IScreenshotProxy> =
+        ServiceConnector.Impl(
+            context,
+            Intent(context, ScreenshotProxyService::class.java),
+            Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
+            context.userId,
+            IScreenshotProxy.Stub::asInterface,
+        )
+
+    private suspend fun dismissKeyguard() {
+        val completion = CompletableDeferred<Unit>()
+        val onDoneBinder =
+            object : IOnDoneCallback.Stub() {
+                override fun onDone(success: Boolean) {
+                    completion.complete(Unit)
+                }
+            }
+        proxyConnector.post { it.dismissKeyguard(onDoneBinder) }
+        completion.await()
+    }
+
+    private fun getCrossProfileConnector(userId: Int): ServiceConnector<ICrossProfileService> =
+        ServiceConnector.Impl<ICrossProfileService>(
+            context,
+            Intent(context, ScreenshotCrossProfileService::class.java),
+            Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
+            userId,
+            ICrossProfileService.Stub::asInterface,
+        )
+
+    private suspend fun launchCrossProfileIntent(userId: Int, intent: Intent, bundle: Bundle) {
+        val connector = getCrossProfileConnector(userId)
+        val completion = CompletableDeferred<Unit>()
+        connector.post {
+            it.launchIntent(intent, bundle)
+            completion.complete(Unit)
+        }
+        completion.await()
+    }
+}
+
+private const val TAG: String = "ActionIntentExecutor"
+private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"
+
+/**
+ * This is effectively a no-op, but we need something non-null to pass in, in order to successfully
+ * override the pending activity entrance animation.
+ */
+private val SCREENSHOT_REMOTE_RUNNER: IRemoteAnimationRunner.Stub =
+    object : IRemoteAnimationRunner.Stub() {
+        override fun onAnimationStart(
+            @WindowManager.TransitionOldType transit: Int,
+            apps: Array<RemoteAnimationTarget>,
+            wallpapers: Array<RemoteAnimationTarget>,
+            nonApps: Array<RemoteAnimationTarget>,
+            finishedCallback: IRemoteAnimationFinishedCallback,
+        ) {
+            try {
+                finishedCallback.onAnimationFinished()
+            } catch (e: RemoteException) {
+                Log.e(TAG, "Error finishing screenshot remote animation", e)
+            }
+        }
+
+        override fun onAnimationCancelled(isKeyguardOccluded: Boolean) {}
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ICrossProfileService.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/ICrossProfileService.aidl
new file mode 100644
index 0000000..da83472
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ICrossProfileService.aidl
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2009, 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.screenshot;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+
+/** Interface implemented by ScreenshotCrossProfileService */
+interface ICrossProfileService {
+
+    void launchIntent(in Intent intent, in Bundle bundle);
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/IOnDoneCallback.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/IOnDoneCallback.aidl
new file mode 100644
index 0000000..e15030f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/IOnDoneCallback.aidl
@@ -0,0 +1,21 @@
+/**
+ * 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.screenshot;
+
+interface IOnDoneCallback {
+  void onDone(boolean success);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl
index f7c4dad..d2e3fbd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl
@@ -16,9 +16,14 @@
 
 package com.android.systemui.screenshot;
 
+import com.android.systemui.screenshot.IOnDoneCallback;
+
 /** Interface implemented by ScreenshotProxyService */
 interface IScreenshotProxy {
 
     /** Is the notification shade currently exanded? */
     boolean isNotificationShadeExpanded();
-}
\ No newline at end of file
+
+    /** Attempts to dismiss the keyguard. */
+    void dismissKeyguard(IOnDoneCallback callback);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 077ad35..7143ba2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -173,6 +173,7 @@
             mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri);
             mImageData.quickShareAction = createQuickShareAction(mContext,
                     mQuickShareData.quickShareAction, uri);
+            mImageData.subject = getSubjectString();
 
             mParams.mActionsReadyListener.onActionsReady(mImageData);
             if (DEBUG_CALLBACK) {
@@ -237,8 +238,6 @@
 
             // Create a share intent, this will always go through the chooser activity first
             // which should not trigger auto-enter PiP
-            String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
-            String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
             Intent sharingIntent = new Intent(Intent.ACTION_SEND);
             sharingIntent.setDataAndType(uri, "image/png");
             sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
@@ -248,7 +247,7 @@
                     new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
                     new ClipData.Item(uri));
             sharingIntent.setClipData(clipdata);
-            sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
+            sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getSubjectString());
             sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                     .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
 
@@ -318,7 +317,7 @@
             // by setting the (otherwise unused) request code to the current user id.
             int requestCode = mContext.getUserId();
 
-            // Create a edit action
+            // Create an edit action
             PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode,
                     new Intent(context, ActionProxyReceiver.class)
                             .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent)
@@ -479,4 +478,9 @@
             mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData);
         }
     }
+
+    private String getSubjectString() {
+        String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
+        return String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 6d5121a..231e415 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -174,7 +174,7 @@
         public List<Notification.Action> smartActions;
         public Notification.Action quickShareAction;
         public UserHandle owner;
-
+        public String subject;  // Title for sharing
 
         /**
          * POD for shared element transition.
@@ -195,6 +195,7 @@
             deleteAction = null;
             smartActions = null;
             quickShareAction = null;
+            subject = null;
         }
     }
 
@@ -273,6 +274,7 @@
     private final ScreenshotNotificationSmartActionsProvider
             mScreenshotNotificationSmartActionsProvider;
     private final TimeoutHandler mScreenshotHandler;
+    private final ActionIntentExecutor mActionExecutor;
 
     private ScreenshotView mScreenshotView;
     private Bitmap mScreenBitmap;
@@ -310,7 +312,8 @@
             ActivityManager activityManager,
             TimeoutHandler timeoutHandler,
             BroadcastSender broadcastSender,
-            ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider
+            ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
+            ActionIntentExecutor actionExecutor
     ) {
         mScreenshotSmartActions = screenshotSmartActions;
         mNotificationsController = screenshotNotificationsController;
@@ -340,6 +343,7 @@
         mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
         mWindowManager = mContext.getSystemService(WindowManager.class);
         mFlags = flags;
+        mActionExecutor = actionExecutor;
 
         mAccessibilityManager = AccessibilityManager.getInstance(mContext);
 
@@ -486,7 +490,7 @@
                 // TODO(159460485): Remove this when focus is handled properly in the system
                 setWindowFocusable(false);
             }
-        });
+        }, mActionExecutor, mFlags);
         mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
 
         mScreenshotView.setOnKeyListener((v, keyCode, event) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt
new file mode 100644
index 0000000..2e6c756
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.screenshot
+
+import android.app.Service
+import android.content.Intent
+import android.os.Bundle
+import android.os.IBinder
+import android.util.Log
+
+/**
+ * If a screenshot is saved to the work profile, any intents that grant access to the screenshot
+ * must come from a service running as the work profile user. This service is meant to be started as
+ * the desired user and just startActivity for the given intent.
+ */
+class ScreenshotCrossProfileService : Service() {
+
+    private val mBinder: IBinder =
+        object : ICrossProfileService.Stub() {
+            override fun launchIntent(intent: Intent, bundle: Bundle) {
+                startActivity(intent, bundle)
+            }
+        }
+
+    override fun onBind(intent: Intent): IBinder? {
+        Log.d(TAG, "onBind: $intent")
+        return mBinder
+    }
+
+    companion object {
+        const val TAG = "ScreenshotProxyService"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index 793085a..c41e2bc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -20,13 +20,16 @@
 import android.os.IBinder
 import android.util.Log
 import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import java.util.Optional
 import javax.inject.Inject
 
 /**
  * Provides state from the main SystemUI process on behalf of the Screenshot process.
  */
 internal class ScreenshotProxyService @Inject constructor(
-    private val mExpansionMgr: ShadeExpansionStateManager
+    private val mExpansionMgr: ShadeExpansionStateManager,
+    private val mCentralSurfacesOptional: Optional<CentralSurfaces>,
 ) : Service() {
 
     private val mBinder: IBinder = object : IScreenshotProxy.Stub() {
@@ -38,6 +41,20 @@
             Log.d(TAG, "isNotificationShadeExpanded(): $expanded")
             return expanded
         }
+
+        override fun dismissKeyguard(callback: IOnDoneCallback) {
+            if (mCentralSurfacesOptional.isPresent) {
+                mCentralSurfacesOptional.get().executeRunnableDismissingKeyguard(
+                    Runnable {
+                        callback.onDone(true)
+                    }, null,
+                    true /* dismissShade */, true /* afterKeyguardGone */,
+                    true /* deferred */
+                )
+            } else {
+                callback.onDone(false)
+            }
+        }
     }
 
     override fun onBind(intent: Intent): IBinder? {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index be41a6b..1b9cdd4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -87,6 +87,8 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.shared.system.InputMonitorCompat;
@@ -168,6 +170,8 @@
 
     private final InteractionJankMonitor mInteractionJankMonitor;
     private long mDefaultTimeoutOfTimeoutHandler;
+    private ActionIntentExecutor mActionExecutor;
+    private FeatureFlags mFlags;
 
     private enum PendingInteraction {
         PREVIEW,
@@ -422,9 +426,12 @@
      * Note: must be called before any other (non-constructor) method or null pointer exceptions
      * may occur.
      */
-    void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks) {
+    void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks,
+            ActionIntentExecutor actionExecutor, FeatureFlags flags) {
         mUiEventLogger = uiEventLogger;
         mCallbacks = callbacks;
+        mActionExecutor = actionExecutor;
+        mFlags = flags;
     }
 
     void setScreenshot(Bitmap bitmap, Insets screenInsets) {
@@ -759,18 +766,41 @@
     void setChipIntents(ScreenshotController.SavedImageData imageData) {
         mShareChip.setOnClickListener(v -> {
             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED, 0, mPackageName);
-            startSharedTransition(
-                    imageData.shareTransition.get());
+            if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
+                prepareSharedTransition();
+                mActionExecutor.launchIntentAsync(
+                        ActionIntentCreator.INSTANCE.createShareIntent(
+                                imageData.uri, imageData.subject),
+                        imageData.shareTransition.get().bundle,
+                        imageData.owner.getIdentifier(), false);
+            } else {
+                startSharedTransition(imageData.shareTransition.get());
+            }
         });
         mEditChip.setOnClickListener(v -> {
             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED, 0, mPackageName);
-            startSharedTransition(
-                    imageData.editTransition.get());
+            if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
+                prepareSharedTransition();
+                mActionExecutor.launchIntentAsync(
+                        ActionIntentCreator.INSTANCE.createEditIntent(imageData.uri, mContext),
+                        imageData.editTransition.get().bundle,
+                        imageData.owner.getIdentifier(), true);
+            } else {
+                startSharedTransition(imageData.editTransition.get());
+            }
         });
         mScreenshotPreview.setOnClickListener(v -> {
             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED, 0, mPackageName);
-            startSharedTransition(
-                    imageData.editTransition.get());
+            if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
+                prepareSharedTransition();
+                mActionExecutor.launchIntentAsync(
+                        ActionIntentCreator.INSTANCE.createEditIntent(imageData.uri, mContext),
+                        imageData.editTransition.get().bundle,
+                        imageData.owner.getIdentifier(), true);
+            } else {
+                startSharedTransition(
+                        imageData.editTransition.get());
+            }
         });
         if (mQuickShareChip != null) {
             mQuickShareChip.setPendingIntent(imageData.quickShareAction.actionIntent,
@@ -1038,6 +1068,12 @@
         }
     }
 
+    private void prepareSharedTransition() {
+        mPendingSharedTransition = true;
+        // fade out non-preview UI
+        createScreenshotFadeDismissAnimation().start();
+    }
+
     ValueAnimator createScreenshotFadeDismissAnimation() {
         ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
         alphaAnim.addUpdateListener(animation -> {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 6e9f859..d5a3954 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -20,6 +20,7 @@
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
 import android.app.Activity;
+import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
 import android.view.Gravity;
@@ -36,6 +37,8 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
 
+import java.util.List;
+
 import javax.inject.Inject;
 
 /** A dialog that provides controls for adjusting the screen brightness. */
@@ -83,6 +86,15 @@
         lp.leftMargin = horizontalMargin;
         lp.rightMargin = horizontalMargin;
         frame.setLayoutParams(lp);
+        Rect bounds = new Rect();
+        frame.addOnLayoutChangeListener(
+                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                    // Exclude this view (and its horizontal margins) from triggering gestures.
+                    // This prevents back gesture from being triggered by dragging close to the
+                    // edge of the slider (0% or 100%).
+                    bounds.set(-horizontalMargin, 0, right - left + horizontalMargin, bottom - top);
+                    v.setSystemGestureExclusionRects(List.of(bounds));
+                });
 
         BrightnessSliderController controller = mToggleSliderFactory.create(this, frame);
         controller.init();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index d3ed474..6b540aa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -280,6 +280,9 @@
                     context.getString(com.android.internal.R.string.status_bar_alarm_clock)
             )
         }
+        if (combinedHeaders) {
+            privacyIconsController.onParentVisible()
+        }
     }
 
     override fun onViewAttached() {
@@ -289,6 +292,7 @@
             clock.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
                 val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f
                 v.pivotX = newPivot
+                v.pivotY = v.height.toFloat() / 2
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
index 07e8b9f..754036d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
@@ -16,7 +16,7 @@
 import android.view.MotionEvent
 import com.android.systemui.dump.DumpsysTableLogger
 import com.android.systemui.dump.Row
-import com.android.systemui.util.collection.RingBuffer
+import com.android.systemui.plugins.util.RingBuffer
 import java.text.SimpleDateFormat
 import java.util.Locale
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index e331812..ddb57f7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -79,7 +79,10 @@
 import android.os.VibrationEffect;
 import android.provider.Settings;
 import android.transition.ChangeBounds;
+import android.transition.Transition;
 import android.transition.TransitionManager;
+import android.transition.TransitionSet;
+import android.transition.TransitionValues;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.MathUtils;
@@ -144,10 +147,12 @@
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
-import com.android.systemui.media.KeyguardMediaController;
-import com.android.systemui.media.MediaDataManager;
-import com.android.systemui.media.MediaHierarchyManager;
+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.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.FalsingManager.FalsingTapListener;
@@ -580,6 +585,7 @@
     private final SysUiState mSysUiState;
 
     private final NotificationShadeDepthController mDepthController;
+    private final NavigationBarController mNavigationBarController;
     private final int mDisplayId;
 
     private KeyguardIndicationController mKeyguardIndicationController;
@@ -689,6 +695,7 @@
     private int mScreenCornerRadius;
     private boolean mQSAnimatingHiddenFromCollapsed;
     private boolean mUseLargeScreenShadeHeader;
+    private boolean mEnableQsClipping;
 
     private int mQsClipTop;
     private int mQsClipBottom;
@@ -857,6 +864,7 @@
             PrivacyDotViewController privacyDotViewController,
             TapAgainViewController tapAgainViewController,
             NavigationModeController navigationModeController,
+            NavigationBarController navigationBarController,
             FragmentService fragmentService,
             ContentResolver contentResolver,
             RecordingController recordingController,
@@ -950,6 +958,7 @@
         mNotificationsQSContainerController = notificationsQSContainerController;
         mNotificationListContainer = notificationListContainer;
         mNotificationStackSizeCalculator = notificationStackSizeCalculator;
+        mNavigationBarController = navigationBarController;
         mKeyguardBottomAreaViewControllerProvider = keyguardBottomAreaViewControllerProvider;
         mNotificationsQSContainerController.init();
         mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
@@ -1298,6 +1307,8 @@
 
         mSplitShadeFullTransitionDistance =
                 mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance);
+
+        mEnableQsClipping = mResources.getBoolean(R.bool.qs_enable_clipping);
     }
 
     private void onSplitShadeEnabledChanged() {
@@ -1437,6 +1448,16 @@
         mMaxAllowedKeyguardNotifications = maxAllowed;
     }
 
+    @VisibleForTesting
+    boolean getClosing() {
+        return mClosing;
+    }
+
+    @VisibleForTesting
+    boolean getIsFlinging() {
+        return mIsFlinging;
+    }
+
     private void updateMaxDisplayedNotifications(boolean recompute) {
         if (recompute) {
             setMaxDisplayedNotifications(Math.max(computeMaxKeyguardNotifications(), 1));
@@ -1664,9 +1685,40 @@
                     // horizontally properly.
                     transition.excludeTarget(R.id.status_view_media_container, true);
                 }
+
                 transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
                 transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-                TransitionManager.beginDelayedTransition(mNotificationContainerParent, transition);
+
+                boolean customClockAnimation =
+                            mKeyguardStatusViewController.getClockAnimations() != null
+                            && mKeyguardStatusViewController.getClockAnimations()
+                                    .getHasCustomPositionUpdatedAnimation();
+
+                if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) {
+                    // Find the clock, so we can exclude it from this transition.
+                    FrameLayout clockContainerView =
+                            mView.findViewById(R.id.lockscreen_clock_view_large);
+                    View clockView = clockContainerView.getChildAt(0);
+
+                    transition.excludeTarget(clockView, /* exclude= */ true);
+
+                    TransitionSet set = new TransitionSet();
+                    set.addTransition(transition);
+
+                    SplitShadeTransitionAdapter adapter =
+                            new SplitShadeTransitionAdapter(mKeyguardStatusViewController);
+
+                    // Use linear here, so the actual clock can pick its own interpolator.
+                    adapter.setInterpolator(Interpolators.LINEAR);
+                    adapter.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+                    adapter.addTarget(clockView);
+                    set.addTransition(adapter);
+
+                    TransitionManager.beginDelayedTransition(mNotificationContainerParent, set);
+                } else {
+                    TransitionManager.beginDelayedTransition(
+                            mNotificationContainerParent, transition);
+                }
             }
 
             constraintSet.applyTo(mNotificationContainerParent);
@@ -2090,7 +2142,8 @@
         animator.start();
     }
 
-    private void onFlingEnd(boolean cancelled) {
+    @VisibleForTesting
+    void onFlingEnd(boolean cancelled) {
         mIsFlinging = false;
         // No overshoot when the animation ends
         setOverExpansionInternal(0, false /* isFromGesture */);
@@ -2164,7 +2217,8 @@
                     mShadeLog.logMotionEvent(event,
                             "onQsIntercept: move ignored because qs tracking disabled");
                 }
-                if ((h > getTouchSlop(event) || (h < -getTouchSlop(event) && mQsExpanded))
+                float touchSlop = getTouchSlop(event);
+                if ((h > touchSlop || (h < -touchSlop && mQsExpanded))
                         && Math.abs(h) > Math.abs(x - mInitialTouchX)
                         && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
                     if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept - start tracking expansion");
@@ -2179,6 +2233,9 @@
                     mInitialTouchX = x;
                     mNotificationStackScrollLayoutController.cancelLongPress();
                     return true;
+                } else {
+                    mShadeLog.logQsTrackingNotStarted(mInitialTouchY, y, h, touchSlop, mQsExpanded,
+                            mCollapsedOnDown, mKeyguardShowing, isQsExpansionEnabled());
                 }
                 break;
 
@@ -2629,12 +2686,16 @@
             mQsExpanded = expanded;
             updateQsState();
             updateExpandedHeightToMaxHeight();
-            mFalsingCollector.setQsExpanded(expanded);
-            mCentralSurfaces.setQsExpanded(expanded);
-            mNotificationsQSContainerController.setQsExpanded(expanded);
-            mPulseExpansionHandler.setQsExpanded(expanded);
-            mKeyguardBypassController.setQSExpanded(expanded);
-            mPrivacyDotViewController.setQsExpanded(expanded);
+            setStatusAccessibilityImportance(expanded
+                    ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                    : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+            updateSystemUiStateFlags();
+            NavigationBarView navigationBarView =
+                    mNavigationBarController.getNavigationBarView(mDisplayId);
+            if (navigationBarView != null) {
+                navigationBarView.onStatusBarPanelStateChanged();
+            }
+            mShadeExpansionStateManager.onQsExpansionChanged(expanded);
         }
     }
 
@@ -2948,8 +3009,10 @@
             mQsTranslationForFullShadeTransition = qsTranslation;
             updateQsFrameTranslation();
             float currentTranslation = mQsFrame.getTranslationY();
-            mQsClipTop = (int) (top - currentTranslation - mQsFrame.getTop());
-            mQsClipBottom = (int) (bottom - currentTranslation - mQsFrame.getTop());
+            mQsClipTop = mEnableQsClipping
+                    ? (int) (top - currentTranslation - mQsFrame.getTop()) : 0;
+            mQsClipBottom = mEnableQsClipping
+                    ? (int) (bottom - currentTranslation - mQsFrame.getTop()) : 0;
             mQsVisible = qsVisible;
             mQs.setQsVisible(mQsVisible);
             mQs.setFancyClipping(
@@ -3674,6 +3737,11 @@
         setListening(true);
     }
 
+    @VisibleForTesting
+    void setTouchSlopExceeded(boolean isTouchSlopExceeded) {
+        mTouchSlopExceeded = isTouchSlopExceeded;
+    }
+
     public void setOverExpansion(float overExpansion) {
         if (overExpansion == mOverExpansion) {
             return;
@@ -3825,12 +3893,14 @@
         }
     }
 
-    private void setIsClosing(boolean isClosing) {
+    @VisibleForTesting
+    void setIsClosing(boolean isClosing) {
         boolean wasClosing = isClosing();
         mClosing = isClosing;
         if (wasClosing != isClosing) {
             mPanelEventsEmitter.notifyPanelCollapsingChanged(isClosing);
         }
+        mAmbientState.setIsClosing(isClosing);
     }
 
     private void updateDozingVisibilities(boolean animate) {
@@ -3860,12 +3930,16 @@
         switch (mBarState) {
             case KEYGUARD:
                 if (!mDozingOnDown) {
-                    if (mUpdateMonitor.isFaceEnrolled()
-                            && !mUpdateMonitor.isFaceDetectionRunning()
-                            && !mUpdateMonitor.getUserCanSkipBouncer(
-                            KeyguardUpdateMonitor.getCurrentUser())) {
-                        mUpdateMonitor.requestFaceAuth(true,
-                                FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
+                    mShadeLog.v("onMiddleClicked on Keyguard, mDozingOnDown: false");
+                    // Try triggering face auth, this "might" run. Check
+                    // KeyguardUpdateMonitor#shouldListenForFace to see when face auth won't run.
+                    boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(true,
+                            FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
+
+                    if (didFaceAuthRun) {
+                        mUpdateMonitor.requestActiveUnlock(
+                                ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT,
+                                "lockScreenEmptySpaceTap");
                     } else {
                         mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_HINT,
                                 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
@@ -3873,11 +3947,6 @@
                                 .log(LockscreenUiEvent.LOCKSCREEN_LOCK_SHOW_HINT);
                         startUnlockHintAnimation();
                     }
-                    if (mUpdateMonitor.isFaceEnrolled()) {
-                        mUpdateMonitor.requestActiveUnlock(
-                                ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT,
-                                "lockScreenEmptySpaceTap");
-                    }
                 }
                 return true;
             case StatusBarState.SHADE_LOCKED:
@@ -4154,8 +4223,8 @@
     /**
      * Sets the dozing state.
      *
-     * @param dozing              {@code true} when dozing.
-     * @param animate             if transition should be animated.
+     * @param dozing  {@code true} when dozing.
+     * @param animate if transition should be animated.
      */
     public void setDozing(boolean dozing, boolean animate) {
         if (dozing == mDozing) return;
@@ -4295,35 +4364,35 @@
     /**
      * Starts fold to AOD animation.
      *
-     * @param startAction invoked when the animation starts.
-     * @param endAction invoked when the animation finishes, also if it was cancelled.
+     * @param startAction  invoked when the animation starts.
+     * @param endAction    invoked when the animation finishes, also if it was cancelled.
      * @param cancelAction invoked when the animation is cancelled, before endAction.
      */
     public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
             Runnable cancelAction) {
         mView.animate()
-            .translationX(0)
-            .alpha(1f)
-            .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
-            .setInterpolator(EMPHASIZED_DECELERATE)
-            .setListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    startAction.run();
-                }
+                .translationX(0)
+                .alpha(1f)
+                .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
+                .setInterpolator(EMPHASIZED_DECELERATE)
+                .setListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationStart(Animator animation) {
+                        startAction.run();
+                    }
 
-                @Override
-                public void onAnimationCancel(Animator animation) {
-                    cancelAction.run();
-                }
+                    @Override
+                    public void onAnimationCancel(Animator animation) {
+                        cancelAction.run();
+                    }
 
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    endAction.run();
-                }
-            }).setUpdateListener(anim -> {
-                mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
-            }).start();
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        endAction.run();
+                    }
+                }).setUpdateListener(anim -> {
+                    mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
+                }).start();
     }
 
     /**
@@ -4621,14 +4690,16 @@
         Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
     }
 
-    private void notifyExpandingStarted() {
+    @VisibleForTesting
+    void notifyExpandingStarted() {
         if (!mExpanding) {
             mExpanding = true;
             onExpandingStarted();
         }
     }
 
-    private void notifyExpandingFinished() {
+    @VisibleForTesting
+    void notifyExpandingFinished() {
         endClosing();
         if (mExpanding) {
             mExpanding = false;
@@ -4681,8 +4752,10 @@
     /**
      * Maybe vibrate as panel is opened.
      *
-     * @param openingWithTouch Whether the panel is being opened with touch. If the panel is instead
-     * being opened programmatically (such as by the open panel gesture), we always play haptic.
+     * @param openingWithTouch Whether the panel is being opened with touch. If the panel is
+     *                         instead
+     *                         being opened programmatically (such as by the open panel gesture), we
+     *                         always play haptic.
      */
     private void maybeVibrateOnOpening(boolean openingWithTouch) {
         if (mVibrateOnOpening) {
@@ -4728,6 +4801,7 @@
         mAmbientState.setSwipingUp(false);
         if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop
                 || Math.abs(y - mInitialExpandY) > mTouchSlop
+                || (!isFullyExpanded() && !isFullyCollapsed())
                 || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
             mVelocityTracker.computeCurrentVelocity(1000);
             float vel = mVelocityTracker.getYVelocity();
@@ -4847,10 +4921,12 @@
         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
         animator.addListener(new AnimatorListenerAdapter() {
             private boolean mCancelled;
+
             @Override
             public void onAnimationCancel(Animator animation) {
                 mCancelled = true;
             }
+
             @Override
             public void onAnimationEnd(Animator animation) {
                 mIsSpringBackAnimation = false;
@@ -4898,7 +4974,7 @@
         if (isNaN(h)) {
             Log.wtf(TAG, "ExpandedHeight set to NaN");
         }
-        mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
+        mNotificationShadeWindowController.batchApplyWindowLayoutParams(() -> {
             if (mExpandLatencyTracking && h != 0f) {
                 DejankUtils.postAfterTraversal(
                         () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
@@ -5089,7 +5165,7 @@
     /**
      * Create an animator that can also overshoot
      *
-     * @param targetHeight the target height
+     * @param targetHeight    the target height
      * @param overshootAmount the amount of overshoot desired
      */
     private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) {
@@ -5125,7 +5201,8 @@
      */
     public void updatePanelExpansionAndVisibility() {
         mShadeExpansionStateManager.onPanelExpansionChanged(
-                mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
+                mExpandedFraction, isExpanded(),
+                mTracking, mExpansionDragDownAmountPx);
         updateVisibility();
     }
 
@@ -5886,7 +5963,7 @@
     public final class TouchHandler implements View.OnTouchListener {
         private long mLastTouchDownTime = -1L;
 
-        /** @see ViewGroup#onInterceptTouchEvent(MotionEvent)  */
+        /** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */
         public boolean onInterceptTouchEvent(MotionEvent event) {
             if (SPEW_LOGCAT) {
                 Log.v(TAG,
@@ -6051,6 +6128,10 @@
                 mShadeLog.logMotionEvent(event, "onTouch: PulseExpansionHandler handled event");
                 return true;
             }
+            if (mPulsing) {
+                mShadeLog.logMotionEvent(event, "onTouch: eat touch, device pulsing");
+                return true;
+            }
             if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
                     && !mNotificationStackScrollLayoutController.isLongPressInProgress()
                     && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
@@ -6073,7 +6154,7 @@
             }
 
             handled |= handleTouch(event);
-            return !mDozing || mPulsing || handled;
+            return !mDozing || handled;
         }
 
         private boolean handleTouch(MotionEvent event) {
@@ -6081,7 +6162,7 @@
                 mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding");
                 return false;
             }
-            if (mTouchDisabled  && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
+            if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
                 mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled");
                 return false;
             }
@@ -6238,4 +6319,54 @@
             loadDimens();
         }
     }
+
+    static class SplitShadeTransitionAdapter extends Transition {
+        private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds";
+        private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS };
+
+        private final KeyguardStatusViewController mController;
+
+        SplitShadeTransitionAdapter(KeyguardStatusViewController controller) {
+            mController = controller;
+        }
+
+        private void captureValues(TransitionValues transitionValues) {
+            Rect boundsRect = new Rect();
+            boundsRect.left = transitionValues.view.getLeft();
+            boundsRect.top = transitionValues.view.getTop();
+            boundsRect.right = transitionValues.view.getRight();
+            boundsRect.bottom = transitionValues.view.getBottom();
+            transitionValues.values.put(PROP_BOUNDS, boundsRect);
+        }
+
+        @Override
+        public void captureEndValues(TransitionValues transitionValues) {
+            captureValues(transitionValues);
+        }
+
+        @Override
+        public void captureStartValues(TransitionValues transitionValues) {
+            captureValues(transitionValues);
+        }
+
+        @Override
+        public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
+                TransitionValues endValues) {
+            ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+
+            Rect from = (Rect) startValues.values.get(PROP_BOUNDS);
+            Rect to = (Rect) endValues.values.get(PROP_BOUNDS);
+
+            anim.addUpdateListener(
+                    animation -> mController.getClockAnimations().onPositionUpdated(
+                            from, to, animation.getAnimatedFraction()));
+
+            return anim;
+        }
+
+        @Override
+        public String[] getTransitionProperties() {
+            return TRANSITION_PROPERTIES;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 1d92105..66a22f4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -135,7 +135,8 @@
             DumpManager dumpManager,
             KeyguardStateController keyguardStateController,
             ScreenOffAnimationController screenOffAnimationController,
-            AuthController authController) {
+            AuthController authController,
+            ShadeExpansionStateManager shadeExpansionStateManager) {
         mContext = context;
         mWindowManager = windowManager;
         mActivityManager = activityManager;
@@ -156,6 +157,7 @@
                 .addCallback(mStateListener,
                         SysuiStatusBarStateController.RANK_STATUS_BAR_WINDOW_CONTROLLER);
         configurationController.addCallback(this);
+        shadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
 
         float desiredPreferredRefreshRate = context.getResources()
                 .getInteger(R.integer.config_keyguardRefreshRate);
@@ -607,8 +609,7 @@
         apply(mCurrentState);
     }
 
-    @Override
-    public void setQsExpanded(boolean expanded) {
+    private void onQsExpansionChanged(Boolean expanded) {
         mCurrentState.mQsExpanded = expanded;
         apply(mCurrentState);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index d6f0de8..73c6d50 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -36,17 +36,12 @@
     private val navigationModeController: NavigationModeController,
     private val overviewProxyService: OverviewProxyService,
     private val largeScreenShadeHeaderController: LargeScreenShadeHeaderController,
+    private val shadeExpansionStateManager: ShadeExpansionStateManager,
     private val featureFlags: FeatureFlags,
     @Main private val delayableExecutor: DelayableExecutor
 ) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
 
-    var qsExpanded = false
-        set(value) {
-            if (field != value) {
-                field = value
-                mView.invalidate()
-            }
-        }
+    private var qsExpanded = false
     private var splitShadeEnabled = false
     private var isQSDetailShowing = false
     private var isQSCustomizing = false
@@ -71,6 +66,13 @@
             taskbarVisible = visible
         }
     }
+    private val shadeQsExpansionListener: ShadeQsExpansionListener =
+        ShadeQsExpansionListener { isQsExpanded ->
+            if (qsExpanded != isQsExpanded) {
+                qsExpanded = isQsExpanded
+                mView.invalidate()
+            }
+        }
 
     // With certain configuration changes (like light/dark changes), the nav bar will disappear
     // for a bit, causing `bottomStableInsets` to be unstable for some time. Debounce the value
@@ -106,6 +108,7 @@
     public override fun onViewAttached() {
         updateResources()
         overviewProxyService.addCallback(taskbarVisibilityListener)
+        shadeExpansionStateManager.addQsExpansionListener(shadeQsExpansionListener)
         mView.setInsetsChangedListener(delayedInsetSetter)
         mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) }
         mView.setConfigurationChangedListener { updateResources() }
@@ -113,6 +116,7 @@
 
     override fun onViewDetached() {
         overviewProxyService.removeCallback(taskbarVisibilityListener)
+        shadeExpansionStateManager.removeQsExpansionListener(shadeQsExpansionListener)
         mView.removeOnInsetsChangedListener()
         mView.removeQSFragmentAttachedListener()
         mView.setConfigurationChangedListener(null)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index f617d47..7bba74a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -21,6 +21,7 @@
 import androidx.annotation.FloatRange
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.util.Compile
+import java.util.concurrent.CopyOnWriteArrayList
 import javax.inject.Inject
 
 /**
@@ -31,12 +32,14 @@
 @SysUISingleton
 class ShadeExpansionStateManager @Inject constructor() {
 
-    private val expansionListeners = mutableListOf<ShadeExpansionListener>()
-    private val stateListeners = mutableListOf<ShadeStateListener>()
+    private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>()
+    private val qsExpansionListeners = CopyOnWriteArrayList<ShadeQsExpansionListener>()
+    private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>()
 
     @PanelState private var state: Int = STATE_CLOSED
     @FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f
     private var expanded: Boolean = false
+    private var qsExpanded: Boolean = false
     private var tracking: Boolean = false
     private var dragDownPxAmount: Float = 0f
 
@@ -57,6 +60,15 @@
         expansionListeners.remove(listener)
     }
 
+    fun addQsExpansionListener(listener: ShadeQsExpansionListener) {
+        qsExpansionListeners.add(listener)
+        listener.onQsExpansionChanged(qsExpanded)
+    }
+
+    fun removeQsExpansionListener(listener: ShadeQsExpansionListener) {
+        qsExpansionListeners.remove(listener)
+    }
+
     /** Adds a listener that will be notified when the panel state has changed. */
     fun addStateListener(listener: ShadeStateListener) {
         stateListeners.add(listener)
@@ -126,6 +138,14 @@
         expansionListeners.forEach { it.onPanelExpansionChanged(expansionChangeEvent) }
     }
 
+    /** Called when the quick settings expansion changes to fully expanded or collapsed. */
+    fun onQsExpansionChanged(qsExpanded: Boolean) {
+        this.qsExpanded = qsExpanded
+
+        debugLog("qsExpanded=$qsExpanded")
+        qsExpansionListeners.forEach { it.onQsExpansionChanged(qsExpanded) }
+    }
+
     /** Updates the panel state if necessary. */
     fun updateState(@PanelState state: Int) {
         debugLog(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index f1e44ce..2b788d8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -1,20 +1,17 @@
 package com.android.systemui.shade
 
 import android.view.MotionEvent
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogMessage
 import com.android.systemui.log.dagger.ShadeLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogMessage
 import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
 
 private const val TAG = "systemui.shade"
 
 /** Lightweight logging utility for the Shade. */
-class ShadeLogger @Inject constructor(
-    @ShadeLog
-    private val buffer: LogBuffer
-) {
+class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) {
     fun v(@CompileTimeConstant msg: String) {
         buffer.log(TAG, LogLevel.VERBOSE, msg)
     }
@@ -28,21 +25,56 @@
     }
 
     fun onQsInterceptMoveQsTrackingEnabled(h: Float) {
-        log(LogLevel.VERBOSE,
+        log(
+            LogLevel.VERBOSE,
             { double1 = h.toDouble() },
-            { "onQsIn[tercept: move action, QS tracking enabled. h = $double1" })
+            { "onQsIntercept: move action, QS tracking enabled. h = $double1" }
+        )
+    }
+
+    fun logQsTrackingNotStarted(
+        initialTouchY: Float,
+        y: Float,
+        h: Float,
+        touchSlop: Float,
+        qsExpanded: Boolean,
+        collapsedOnDown: Boolean,
+        keyguardShowing: Boolean,
+        qsExpansionEnabled: Boolean
+    ) {
+        log(
+            LogLevel.VERBOSE,
+            {
+                int1 = initialTouchY.toInt()
+                int2 = y.toInt()
+                long1 = h.toLong()
+                double1 = touchSlop.toDouble()
+                bool1 = qsExpanded
+                bool2 = collapsedOnDown
+                bool3 = keyguardShowing
+                bool4 = qsExpansionEnabled
+            },
+            {
+                "QsTrackingNotStarted: initTouchY=$int1,y=$int2,h=$long1,slop=$double1,qsExpanded" +
+                    "=$bool1,collapsedDown=$bool2,keyguardShowing=$bool3,qsExpansion=$bool4"
+            }
+        )
     }
 
     fun logMotionEvent(event: MotionEvent, message: String) {
-        log(LogLevel.VERBOSE, {
-            str1 = message
-            long1 = event.eventTime
-            long2 = event.downTime
-            int1 = event.action
-            int2 = event.classification
-            double1 = event.y.toDouble()
-        }, {
-            "$str1\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,classification=$int2"
-        })
+        log(
+            LogLevel.VERBOSE,
+            {
+                str1 = message
+                long1 = event.eventTime
+                long2 = event.downTime
+                int1 = event.action
+                int2 = event.classification
+                double1 = event.y.toDouble()
+            },
+            {
+                "$str1\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2"
+            }
+        )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeQsExpansionListener.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeQsExpansionListener.kt
new file mode 100644
index 0000000..14882b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeQsExpansionListener.kt
@@ -0,0 +1,25 @@
+/*
+ * 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
+
+/** A listener interface to be notified of expansion events for the quick settings panel. */
+fun interface ShadeQsExpansionListener {
+    /**
+     * Invoked whenever the quick settings expansion changes, when it is fully collapsed or expanded
+     */
+    fun onQsExpansionChanged(isQsExpanded: Boolean)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
new file mode 100644
index 0000000..09019a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.ShadeExpansionListener
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.domain.model.ShadeModel
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+
+/** Business logic for shade interactions */
+@SysUISingleton
+class ShadeRepository @Inject constructor(shadeExpansionStateManager: ShadeExpansionStateManager) {
+
+    val shadeModel: Flow<ShadeModel> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : ShadeExpansionListener {
+                        override fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) {
+                            // Don't propagate ShadeExpansionChangeEvent.dragDownPxAmount field.
+                            // It is too noisy and produces extra events that consumers won't care
+                            // about
+                            val info =
+                                ShadeModel(
+                                    expansionAmount = event.fraction,
+                                    isExpanded = event.expanded,
+                                    isUserDragging = event.tracking
+                                )
+                            trySendWithFailureLogging(info, TAG, "updated shade expansion info")
+                        }
+                    }
+
+                shadeExpansionStateManager.addExpansionListener(callback)
+                trySendWithFailureLogging(ShadeModel(), TAG, "initial shade expansion info")
+
+                awaitClose { shadeExpansionStateManager.removeExpansionListener(callback) }
+            }
+            .distinctUntilChanged()
+
+    companion object {
+        private const val TAG = "ShadeRepository"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt
new file mode 100644
index 0000000..ce0f4283
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.domain.model
+
+import android.annotation.FloatRange
+
+/** Information about shade (NotificationPanel) expansion */
+data class ShadeModel(
+    /** 0 when collapsed, 1 when fully expanded. */
+    @FloatRange(from = 0.0, to = 1.0) val expansionAmount: Float = 0f,
+    /** Whether the panel should be considered expanded */
+    val isExpanded: Boolean = false,
+    /** Whether the user is actively dragging the panel. */
+    val isUserDragging: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
index 7f7ff9cf..90c52bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
@@ -17,9 +17,9 @@
 package com.android.systemui.statusbar
 
 import android.app.PendingIntent
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.NotifInteractionLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 04621168..e6d7e41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -69,12 +69,15 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.GcUtils;
 import com.android.internal.view.AppearanceRegion;
+import com.android.systemui.dump.DumpHandler;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.commandline.CommandRegistry;
 import com.android.systemui.statusbar.policy.CallbackController;
 import com.android.systemui.tracing.ProtoTracer;
 
+import java.io.FileDescriptor;
 import java.io.FileOutputStream;
+import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
@@ -184,6 +187,7 @@
     private int mLastUpdatedImeDisplayId = INVALID_DISPLAY;
     private ProtoTracer mProtoTracer;
     private final @Nullable CommandRegistry mRegistry;
+    private final @Nullable DumpHandler mDumpHandler;
 
     /**
      * These methods are called back on the main thread.
@@ -473,12 +477,18 @@
     }
 
     public CommandQueue(Context context) {
-        this(context, null, null);
+        this(context, null, null, null);
     }
 
-    public CommandQueue(Context context, ProtoTracer protoTracer, CommandRegistry registry) {
+    public CommandQueue(
+            Context context,
+            ProtoTracer protoTracer,
+            CommandRegistry registry,
+            DumpHandler dumpHandler
+    ) {
         mProtoTracer = protoTracer;
         mRegistry = registry;
+        mDumpHandler = dumpHandler;
         context.getSystemService(DisplayManager.class).registerDisplayListener(this, mHandler);
         // We always have default display.
         setDisabled(DEFAULT_DISPLAY, DISABLE_NONE, DISABLE2_NONE);
@@ -1178,6 +1188,35 @@
     }
 
     @Override
+    public void dumpProto(String[] args, ParcelFileDescriptor pfd) {
+        final FileDescriptor fd = pfd.getFileDescriptor();
+        // This is mimicking Binder#dumpAsync, but on this side of the binder. Might be possible
+        // to just throw this work onto the handler just like the other messages
+        Thread thr = new Thread("Sysui.dumpProto") {
+            public void run() {
+                try {
+                    if (mDumpHandler == null) {
+                        return;
+                    }
+                    // We won't be using the PrintWriter.
+                    OutputStream o = new OutputStream() {
+                        @Override
+                        public void write(int b) {}
+                    };
+                    mDumpHandler.dump(fd, new PrintWriter(o), args);
+                } finally {
+                    try {
+                        // Close the file descriptor so the TransferPipe finishes its thread
+                        pfd.close();
+                    } catch (Exception e) {
+                    }
+                }
+            }
+        };
+        thr.start();
+    }
+
+    @Override
     public void runGcForTest() {
         // Gc sysui
         GcUtils.runGcAndFinalizersSync();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt
index 886ad68..5fb5002 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt
@@ -5,7 +5,7 @@
 import android.util.MathUtils
 import com.android.systemui.R
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.MediaHierarchyManager
 import com.android.systemui.shade.NotificationPanelViewController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import dagger.assisted.Assisted
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 8006931..a2e4536 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.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.media.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.MediaHierarchyManager
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.qs.QS
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 4be5a1a..ced725e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -48,9 +48,9 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaData;
-import com.android.systemui.media.MediaDataManager;
-import com.android.systemui.media.SmartspaceMediaData;
+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.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index 0c9e1ec..e21acb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -92,9 +92,6 @@
     /** Sets the state of whether the keyguard is fading away or not. */
     default void setKeyguardFadingAway(boolean keyguardFadingAway) {}
 
-    /** Sets the state of whether the quick settings is expanded or not. */
-    default void setQsExpanded(boolean expanded) {}
-
     /** Sets the state of whether the user activities are forced or not. */
     default void setForceUserActivity(boolean forceUserActivity) {}
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index f961984..87ef92a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -40,6 +40,7 @@
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -110,8 +111,8 @@
         setClipChildren(false);
         setClipToPadding(false);
         mShelfIcons.setIsStaticLayout(false);
-        setBottomRoundness(1.0f, false /* animate */);
-        setTopRoundness(1f, false /* animate */);
+        requestBottomRoundness(1.0f, /* animate = */ false, SourceType.DefaultValue);
+        requestTopRoundness(1f, false, SourceType.DefaultValue);
 
         // Setting this to first in section to get the clipping to the top roundness correct. This
         // value determines the way we are clipping to the top roundness of the overall shade
@@ -413,7 +414,7 @@
                     if (iconState != null && iconState.clampedAppearAmount == 1.0f) {
                         // only if the first icon is fully in the shelf we want to clip to it!
                         backgroundTop = (int) (child.getTranslationY() - getTranslationY());
-                        firstElementRoundness = expandableRow.getCurrentTopRoundness();
+                        firstElementRoundness = expandableRow.getTopRoundness();
                     }
                 }
 
@@ -507,28 +508,36 @@
             // Round bottom corners within animation bounds
             final float changeFraction = MathUtils.saturate(
                     (viewEnd - cornerAnimationTop) / cornerAnimationDistance);
-            anv.setBottomRoundness(anv.isLastInSection() ? 1f : changeFraction,
-                    false /* animate */);
+            anv.requestBottomRoundness(
+                    anv.isLastInSection() ? 1f : changeFraction,
+                    /* animate = */ false,
+                    SourceType.OnScroll);
 
         } else if (viewEnd < cornerAnimationTop) {
             // Fast scroll skips frames and leaves corners with unfinished rounding.
             // Reset top and bottom corners outside of animation bounds.
-            anv.setBottomRoundness(anv.isLastInSection() ? 1f : smallCornerRadius,
-                    false /* animate */);
+            anv.requestBottomRoundness(
+                    anv.isLastInSection() ? 1f : smallCornerRadius,
+                    /* animate = */ false,
+                    SourceType.OnScroll);
         }
 
         if (viewStart >= cornerAnimationTop) {
             // Round top corners within animation bounds
             final float changeFraction = MathUtils.saturate(
                     (viewStart - cornerAnimationTop) / cornerAnimationDistance);
-            anv.setTopRoundness(anv.isFirstInSection() ? 1f : changeFraction,
-                    false /* animate */);
+            anv.requestTopRoundness(
+                    anv.isFirstInSection() ? 1f : changeFraction,
+                    false,
+                    SourceType.OnScroll);
 
         } else if (viewStart < cornerAnimationTop) {
             // Fast scroll skips frames and leaves corners with unfinished rounding.
             // Reset top and bottom corners outside of animation bounds.
-            anv.setTopRoundness(anv.isFirstInSection() ? 1f : smallCornerRadius,
-                    false /* animate */);
+            anv.requestTopRoundness(
+                    anv.isFirstInSection() ? 1f : smallCornerRadius,
+                    false,
+                    SourceType.OnScroll);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index bbff046..c630feb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -52,7 +53,10 @@
 import kotlin.math.max
 
 /**
- * A utility class to enable the downward swipe on when pulsing.
+ * A utility class that handles notification panel expansion when a user swipes downward on a
+ * notification from the pulsing state.
+ * If face-bypass is enabled, the user can swipe down anywhere on the screen (not just from a
+ * notification) to trigger the notification panel expansion.
  */
 @SysUISingleton
 class PulseExpansionHandler @Inject
@@ -62,9 +66,10 @@
     private val bypassController: KeyguardBypassController,
     private val headsUpManager: HeadsUpManagerPhone,
     private val roundnessManager: NotificationRoundnessManager,
-    private val configurationController: ConfigurationController,
+    configurationController: ConfigurationController,
     private val statusBarStateController: StatusBarStateController,
     private val falsingManager: FalsingManager,
+    shadeExpansionStateManager: ShadeExpansionStateManager,
     private val lockscreenShadeTransitionController: LockscreenShadeTransitionController,
     private val falsingCollector: FalsingCollector,
     dumpManager: DumpManager
@@ -123,6 +128,13 @@
                 initResources(context)
             }
         })
+
+        shadeExpansionStateManager.addQsExpansionListener { isQsExpanded ->
+            if (qsExpanded != isQsExpanded) {
+                qsExpanded = isQsExpanded
+            }
+        }
+
         mPowerManager = context.getSystemService(PowerManager::class.java)
         dumpManager.registerDumpable(this)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index b3dd853..402217d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -71,9 +71,9 @@
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.LogLevel;
 import com.android.systemui.log.dagger.StatusBarNetworkControllerLog;
+import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.plugins.log.LogLevel;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
 import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.statusbar.policy.ConfigurationController;
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 11e3d17..eacb18e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -29,8 +29,9 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpHandler;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaDataManager;
+import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.carrier.QSCarrierGroupController;
@@ -181,8 +182,10 @@
     static CommandQueue provideCommandQueue(
             Context context,
             ProtoTracer protoTracer,
-            CommandRegistry registry) {
-        return new CommandQueue(context, protoTracer, registry);
+            CommandRegistry registry,
+            DumpHandler dumpHandler
+    ) {
+        return new CommandQueue(context, protoTracer, registry, dumpHandler);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index d88f07c..737b481 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -25,11 +25,12 @@
 import android.view.View
 import android.widget.FrameLayout
 import com.android.internal.annotations.GuardedBy
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.StatusBarState.SHADE
 import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener
@@ -42,7 +43,6 @@
 import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE
 import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
 import com.android.systemui.util.leak.RotationUtils.Rotation
-
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
@@ -67,7 +67,8 @@
     private val stateController: StatusBarStateController,
     private val configurationController: ConfigurationController,
     private val contentInsetsProvider: StatusBarContentInsetsProvider,
-    private val animationScheduler: SystemStatusAnimationScheduler
+    private val animationScheduler: SystemStatusAnimationScheduler,
+    shadeExpansionStateManager: ShadeExpansionStateManager
 ) {
     private lateinit var tl: View
     private lateinit var tr: View
@@ -128,6 +129,13 @@
                 updateStatusBarState()
             }
         })
+
+        shadeExpansionStateManager.addQsExpansionListener { isQsExpanded ->
+            dlog("setQsExpanded $isQsExpanded")
+            synchronized(lock) {
+                nextViewState = nextViewState.copy(qsExpanded = isQsExpanded)
+            }
+        }
     }
 
     fun setUiExecutor(e: DelayableExecutor) {
@@ -138,13 +146,6 @@
         showingListener = l
     }
 
-    fun setQsExpanded(expanded: Boolean) {
-        dlog("setQsExpanded $expanded")
-        synchronized(lock) {
-            nextViewState = nextViewState.copy(qsExpanded = expanded)
-        }
-    }
-
     @UiThread
     fun setNewRotation(rot: Int) {
         dlog("updateRotation: $rot")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
index 17feaa8..9bdff92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.gesture
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.SwipeStatusBarAwayLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
 import javax.inject.Inject
 
 /** Log messages for [SwipeStatusBarAwayGestureHandler]. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 8f8813b80..842204b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -118,6 +118,7 @@
                     regionSamplingEnabled,
                     updateFun
             )
+            initializeTextColors(regionSamplingInstance)
             regionSamplingInstance.startRegionSampler()
             regionSamplingInstances.put(v, regionSamplingInstance)
             connectSession()
@@ -361,18 +362,20 @@
         }
     }
 
+    private fun initializeTextColors(regionSamplingInstance: RegionSamplingInstance) {
+        val lightThemeContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_LightWallpaper)
+        val darkColor = Utils.getColorAttrDefaultColor(lightThemeContext, R.attr.wallpaperTextColor)
+
+        val darkThemeContext = ContextThemeWrapper(context, R.style.Theme_SystemUI)
+        val lightColor = Utils.getColorAttrDefaultColor(darkThemeContext, R.attr.wallpaperTextColor)
+
+        regionSamplingInstance.setForegroundColors(lightColor, darkColor)
+    }
+
     private fun updateTextColorFromRegionSampler() {
         smartspaceViews.forEach {
-            val isRegionDark = regionSamplingInstances.getValue(it).currentRegionDarkness()
-            val themeID = if (isRegionDark.isDark) {
-                R.style.Theme_SystemUI
-            } else {
-                R.style.Theme_SystemUI_LightWallpaper
-            }
-            val themedContext = ContextThemeWrapper(context, themeID)
-            val wallpaperTextColor =
-                    Utils.getColorAttrDefaultColor(themedContext, R.attr.wallpaperTextColor)
-            it.setPrimaryTextColor(wallpaperTextColor)
+            val textColor = regionSamplingInstances.getValue(it).currentForegroundColor()
+            it.setPrimaryTextColor(textColor)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
index 822840d..0a5e986 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
@@ -290,7 +290,7 @@
                             .setComponent(aiaComponent)
                             .setAction(Intent.ACTION_VIEW)
                             .addCategory(Intent.CATEGORY_BROWSABLE)
-                            .addCategory("unique:" + System.currentTimeMillis())
+                            .setIdentifier("unique:" + System.currentTimeMillis())
                             .putExtra(Intent.EXTRA_PACKAGE_NAME, appInfo.packageName)
                             .putExtra(
                                     Intent.EXTRA_VERSION_CODE,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 7fbdd35..2734511 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -17,40 +17,27 @@
 package com.android.systemui.statusbar.notification
 
 import android.content.Context
-import android.util.Log
-import android.widget.Toast
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.util.Compile
 import javax.inject.Inject
 
 class NotifPipelineFlags @Inject constructor(
     val context: Context,
     val featureFlags: FeatureFlags
 ) {
-    fun checkLegacyPipelineEnabled(): Boolean {
-        if (Compile.IS_DEBUG) {
-            Toast.makeText(context, "Old pipeline code running!", Toast.LENGTH_SHORT).show()
-        }
-        if (featureFlags.isEnabled(Flags.NEW_PIPELINE_CRASH_ON_CALL_TO_OLD_PIPELINE)) {
-            throw RuntimeException("Old pipeline code running with new pipeline enabled")
-        } else {
-            Log.d("NotifPipeline", "Old pipeline code running with new pipeline enabled",
-                    Exception())
-        }
-        return false
-    }
-
     fun isDevLoggingEnabled(): Boolean =
         featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
 
-    fun isSmartspaceDedupingEnabled(): Boolean =
-            featureFlags.isEnabled(Flags.SMARTSPACE) &&
-                    featureFlags.isEnabled(Flags.SMARTSPACE_DEDUPING)
-
-    fun removeUnrankedNotifs(): Boolean =
-        featureFlags.isEnabled(Flags.REMOVE_UNRANKED_NOTIFICATIONS)
+    fun isSmartspaceDedupingEnabled(): Boolean = featureFlags.isEnabled(Flags.SMARTSPACE)
 
     fun fullScreenIntentRequiresKeyguard(): Boolean =
         featureFlags.isEnabled(Flags.FSI_REQUIRES_KEYGUARD)
+
+    val isStabilityIndexFixEnabled: Boolean by lazy {
+        featureFlags.isEnabled(Flags.STABILITY_INDEX_FIX)
+    }
+
+    val isSemiStableSortEnabled: Boolean by lazy {
+        featureFlags.isEnabled(Flags.SEMI_STABLE_SORT)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
index ad3dfed..3058fbb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.notification
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.NotifInteractionLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
index 553826d..0d35fdc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -70,8 +70,8 @@
         val height = max(0, notification.actualHeight - notification.clipBottomAmount)
         val location = notification.locationOnScreen
 
-        val clipStartLocation = notificationListContainer.getTopClippingStartLocation()
-        val roundedTopClipping = Math.max(clipStartLocation - location[1], 0)
+        val clipStartLocation = notificationListContainer.topClippingStartLocation
+        val roundedTopClipping = (clipStartLocation - location[1]).coerceAtLeast(0)
         val windowTop = location[1] + roundedTopClipping
         val topCornerRadius = if (roundedTopClipping > 0) {
             // Because the rounded Rect clipping is complex, we start the top rounding at
@@ -80,7 +80,7 @@
             // if we'd like to have this perfect, but this is close enough.
             0f
         } else {
-            notification.currentBackgroundRadiusTop
+            notification.topCornerRadius
         }
         val params = LaunchAnimationParameters(
             top = windowTop,
@@ -88,7 +88,7 @@
             left = location[0],
             right = location[0] + notification.width,
             topCornerRadius = topCornerRadius,
-            bottomCornerRadius = notification.currentBackgroundRadiusBottom
+            bottomCornerRadius = notification.bottomCornerRadius
         )
 
         params.startTranslationZ = notification.translationZ
@@ -97,8 +97,8 @@
         params.startClipTopAmount = notification.clipTopAmount
         if (notification.isChildInGroup) {
             params.startNotificationTop += notification.notificationParent.translationY
-            val parentRoundedClip = Math.max(
-                clipStartLocation - notification.notificationParent.locationOnScreen[1], 0)
+            val locationOnScreen = notification.notificationParent.locationOnScreen[1]
+            val parentRoundedClip = (clipStartLocation - locationOnScreen).coerceAtLeast(0)
             params.parentStartRoundedTopClipping = parentRoundedClip
 
             val parentClip = notification.notificationParent.clipTopAmount
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 7242506..d97b712 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -18,8 +18,10 @@
 
 import android.animation.ObjectAnimator
 import android.util.FloatProperty
+import com.android.systemui.Dumpable
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionChangeEvent
 import com.android.systemui.shade.ShadeExpansionListener
@@ -32,17 +34,20 @@
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import java.io.PrintWriter
 import javax.inject.Inject
 import kotlin.math.min
 
 @SysUISingleton
 class NotificationWakeUpCoordinator @Inject constructor(
+    dumpManager: DumpManager,
     private val mHeadsUpManager: HeadsUpManager,
     private val statusBarStateController: StatusBarStateController,
     private val bypassController: KeyguardBypassController,
     private val dozeParameters: DozeParameters,
     private val screenOffAnimationController: ScreenOffAnimationController
-) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, ShadeExpansionListener {
+) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, ShadeExpansionListener,
+    Dumpable {
 
     private val mNotificationVisibility = object : FloatProperty<NotificationWakeUpCoordinator>(
         "notificationVisibility") {
@@ -60,6 +65,7 @@
 
     private var mLinearDozeAmount: Float = 0.0f
     private var mDozeAmount: Float = 0.0f
+    private var mDozeAmountSource: String = "init"
     private var mNotificationVisibleAmount = 0.0f
     private var mNotificationsVisible = false
     private var mNotificationsVisibleForExpansion = false
@@ -142,6 +148,7 @@
         }
 
     init {
+        dumpManager.registerDumpable(this)
         mHeadsUpManager.addListener(this)
         statusBarStateController.addCallback(this)
         addListener(object : WakeUpListener {
@@ -248,13 +255,14 @@
             // Let's notify the scroller that an animation started
             notifyAnimationStart(mLinearDozeAmount == 1.0f)
         }
-        setDozeAmount(linear, eased)
+        setDozeAmount(linear, eased, source = "StatusBar")
     }
 
-    fun setDozeAmount(linear: Float, eased: Float) {
+    fun setDozeAmount(linear: Float, eased: Float, source: String) {
         val changed = linear != mLinearDozeAmount
         mLinearDozeAmount = linear
         mDozeAmount = eased
+        mDozeAmountSource = source
         mStackScrollerController.setDozeAmount(mDozeAmount)
         updateHideAmount()
         if (changed && linear == 0.0f) {
@@ -271,7 +279,7 @@
             // undefined state, so it's an indication that we should do state cleanup. We override
             // the doze amount to 0f (not dozing) so that the notifications are no longer hidden.
             // See: UnlockedScreenOffAnimationController.onFinishedWakingUp()
-            setDozeAmount(0f, 0f)
+            setDozeAmount(0f, 0f, source = "Override: Shade->Shade (lock cancelled by unlock)")
         }
 
         if (overrideDozeAmountIfAnimatingScreenOff(mLinearDozeAmount)) {
@@ -311,12 +319,11 @@
      */
     private fun overrideDozeAmountIfBypass(): Boolean {
         if (bypassController.bypassEnabled) {
-            var amount = 1.0f
-            if (statusBarStateController.state == StatusBarState.SHADE ||
-                statusBarStateController.state == StatusBarState.SHADE_LOCKED) {
-                amount = 0.0f
+            if (statusBarStateController.state == StatusBarState.KEYGUARD) {
+                setDozeAmount(1f, 1f, source = "Override: bypass (keyguard)")
+            } else {
+                setDozeAmount(0f, 0f, source = "Override: bypass (shade)")
             }
-            setDozeAmount(amount, amount)
             return true
         }
         return false
@@ -332,7 +339,7 @@
      */
     private fun overrideDozeAmountIfAnimatingScreenOff(linearDozeAmount: Float): Boolean {
         if (screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()) {
-            setDozeAmount(1f, 1f)
+            setDozeAmount(1f, 1f, source = "Override: animating screen off")
             return true
         }
 
@@ -414,6 +421,26 @@
     private fun shouldAnimateVisibility() =
             dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking
 
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("mLinearDozeAmount: $mLinearDozeAmount")
+        pw.println("mDozeAmount: $mDozeAmount")
+        pw.println("mDozeAmountSource: $mDozeAmountSource")
+        pw.println("mNotificationVisibleAmount: $mNotificationVisibleAmount")
+        pw.println("mNotificationsVisible: $mNotificationsVisible")
+        pw.println("mNotificationsVisibleForExpansion: $mNotificationsVisibleForExpansion")
+        pw.println("mVisibilityAmount: $mVisibilityAmount")
+        pw.println("mLinearVisibilityAmount: $mLinearVisibilityAmount")
+        pw.println("pulseExpanding: $pulseExpanding")
+        pw.println("state: ${StatusBarState.toString(state)}")
+        pw.println("fullyAwake: $fullyAwake")
+        pw.println("wakingUp: $wakingUp")
+        pw.println("willWakeUp: $willWakeUp")
+        pw.println("collapsedEnoughToHide: $collapsedEnoughToHide")
+        pw.println("pulsing: $pulsing")
+        pw.println("notificationsFullyHidden: $notificationsFullyHidden")
+        pw.println("canShowPulsingHuns: $canShowPulsingHuns")
+    }
+
     interface WakeUpListener {
         /**
          * Called whenever the notifications are fully hidden or shown
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
new file mode 100644
index 0000000..ed7f648
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
@@ -0,0 +1,284 @@
+package com.android.systemui.statusbar.notification
+
+import android.util.FloatProperty
+import android.view.View
+import androidx.annotation.FloatRange
+import com.android.systemui.R
+import com.android.systemui.statusbar.notification.stack.AnimationProperties
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator
+import kotlin.math.abs
+
+/**
+ * Interface that allows to request/retrieve top and bottom roundness (a value between 0f and 1f).
+ *
+ * To request a roundness value, an [SourceType] must be specified. In case more origins require
+ * different roundness, for the same property, the maximum value will always be chosen.
+ *
+ * It also returns the current radius for all corners ([updatedRadii]).
+ */
+interface Roundable {
+    /** Properties required for a Roundable */
+    val roundableState: RoundableState
+
+    /** Current top roundness */
+    @get:FloatRange(from = 0.0, to = 1.0)
+    @JvmDefault
+    val topRoundness: Float
+        get() = roundableState.topRoundness
+
+    /** Current bottom roundness */
+    @get:FloatRange(from = 0.0, to = 1.0)
+    @JvmDefault
+    val bottomRoundness: Float
+        get() = roundableState.bottomRoundness
+
+    /** Max radius in pixel */
+    @JvmDefault
+    val maxRadius: Float
+        get() = roundableState.maxRadius
+
+    /** Current top corner in pixel, based on [topRoundness] and [maxRadius] */
+    @JvmDefault
+    val topCornerRadius: Float
+        get() = topRoundness * maxRadius
+
+    /** Current bottom corner in pixel, based on [bottomRoundness] and [maxRadius] */
+    @JvmDefault
+    val bottomCornerRadius: Float
+        get() = bottomRoundness * maxRadius
+
+    /** Get and update the current radii */
+    @JvmDefault
+    val updatedRadii: FloatArray
+        get() =
+            roundableState.radiiBuffer.also { radii ->
+                updateRadii(
+                    topCornerRadius = topCornerRadius,
+                    bottomCornerRadius = bottomCornerRadius,
+                    radii = radii,
+                )
+            }
+
+    /**
+     * Request the top roundness [value] for a specific [sourceType].
+     *
+     * The top roundness of a [Roundable] can be defined by different [sourceType]. In case more
+     * origins require different roundness, for the same property, the maximum value will always be
+     * chosen.
+     *
+     * @param value a value between 0f and 1f.
+     * @param animate true if it should animate to that value.
+     * @param sourceType the source from which the request for roundness comes.
+     * @return Whether the roundness was changed.
+     */
+    @JvmDefault
+    fun requestTopRoundness(
+        @FloatRange(from = 0.0, to = 1.0) value: Float,
+        animate: Boolean,
+        sourceType: SourceType,
+    ): Boolean {
+        val roundnessMap = roundableState.topRoundnessMap
+        val lastValue = roundnessMap.values.maxOrNull() ?: 0f
+        if (value == 0f) {
+            // we should only take the largest value, and since the smallest value is 0f, we can
+            // remove this value from the list. In the worst case, the list is empty and the
+            // default value is 0f.
+            roundnessMap.remove(sourceType)
+        } else {
+            roundnessMap[sourceType] = value
+        }
+        val newValue = roundnessMap.values.maxOrNull() ?: 0f
+
+        if (lastValue != newValue) {
+            val wasAnimating = roundableState.isTopAnimating()
+
+            // Fail safe:
+            // when we've been animating previously and we're now getting an update in the
+            // other direction, make sure to animate it too, otherwise, the localized updating
+            // may make the start larger than 1.0.
+            val shouldAnimate = wasAnimating && abs(newValue - lastValue) > 0.5f
+
+            roundableState.setTopRoundness(value = newValue, animated = shouldAnimate || animate)
+            return true
+        }
+        return false
+    }
+
+    /**
+     * Request the bottom roundness [value] for a specific [sourceType].
+     *
+     * The bottom roundness of a [Roundable] can be defined by different [sourceType]. In case more
+     * origins require different roundness, for the same property, the maximum value will always be
+     * chosen.
+     *
+     * @param value value between 0f and 1f.
+     * @param animate true if it should animate to that value.
+     * @param sourceType the source from which the request for roundness comes.
+     * @return Whether the roundness was changed.
+     */
+    @JvmDefault
+    fun requestBottomRoundness(
+        @FloatRange(from = 0.0, to = 1.0) value: Float,
+        animate: Boolean,
+        sourceType: SourceType,
+    ): Boolean {
+        val roundnessMap = roundableState.bottomRoundnessMap
+        val lastValue = roundnessMap.values.maxOrNull() ?: 0f
+        if (value == 0f) {
+            // we should only take the largest value, and since the smallest value is 0f, we can
+            // remove this value from the list. In the worst case, the list is empty and the
+            // default value is 0f.
+            roundnessMap.remove(sourceType)
+        } else {
+            roundnessMap[sourceType] = value
+        }
+        val newValue = roundnessMap.values.maxOrNull() ?: 0f
+
+        if (lastValue != newValue) {
+            val wasAnimating = roundableState.isBottomAnimating()
+
+            // Fail safe:
+            // when we've been animating previously and we're now getting an update in the
+            // other direction, make sure to animate it too, otherwise, the localized updating
+            // may make the start larger than 1.0.
+            val shouldAnimate = wasAnimating && abs(newValue - lastValue) > 0.5f
+
+            roundableState.setBottomRoundness(value = newValue, animated = shouldAnimate || animate)
+            return true
+        }
+        return false
+    }
+
+    /** Apply the roundness changes, usually means invalidate the [RoundableState.targetView]. */
+    @JvmDefault
+    fun applyRoundness() {
+        roundableState.targetView.invalidate()
+    }
+
+    /** @return true if top or bottom roundness is not zero. */
+    @JvmDefault
+    fun hasRoundedCorner(): Boolean {
+        return topRoundness != 0f || bottomRoundness != 0f
+    }
+
+    /**
+     * Update an Array of 8 values, 4 pairs of [X,Y] radii. As expected by param radii of
+     * [android.graphics.Path.addRoundRect].
+     *
+     * This method reuses the previous [radii] for performance reasons.
+     */
+    @JvmDefault
+    fun updateRadii(
+        topCornerRadius: Float,
+        bottomCornerRadius: Float,
+        radii: FloatArray,
+    ) {
+        if (radii.size != 8) error("Unexpected radiiBuffer size ${radii.size}")
+
+        if (radii[0] != topCornerRadius || radii[4] != bottomCornerRadius) {
+            (0..3).forEach { radii[it] = topCornerRadius }
+            (4..7).forEach { radii[it] = bottomCornerRadius }
+        }
+    }
+}
+
+/**
+ * State object for a `Roundable` class.
+ * @param targetView Will handle the [AnimatableProperty]
+ * @param roundable Target of the radius animation
+ * @param maxRadius Max corner radius in pixels
+ */
+class RoundableState(
+    internal val targetView: View,
+    roundable: Roundable,
+    internal val maxRadius: Float,
+) {
+    /** Animatable for top roundness */
+    private val topAnimatable = topAnimatable(roundable)
+
+    /** Animatable for bottom roundness */
+    private val bottomAnimatable = bottomAnimatable(roundable)
+
+    /** Current top roundness. Use [setTopRoundness] to update this value */
+    @set:FloatRange(from = 0.0, to = 1.0)
+    internal var topRoundness = 0f
+        private set
+
+    /** Current bottom roundness. Use [setBottomRoundness] to update this value */
+    @set:FloatRange(from = 0.0, to = 1.0)
+    internal var bottomRoundness = 0f
+        private set
+
+    /** Last requested top roundness associated by [SourceType] */
+    internal val topRoundnessMap = mutableMapOf<SourceType, Float>()
+
+    /** Last requested bottom roundness associated by [SourceType] */
+    internal val bottomRoundnessMap = mutableMapOf<SourceType, Float>()
+
+    /** Last cached radii */
+    internal val radiiBuffer = FloatArray(8)
+
+    /** Is top roundness animation in progress? */
+    internal fun isTopAnimating() = PropertyAnimator.isAnimating(targetView, topAnimatable)
+
+    /** Is bottom roundness animation in progress? */
+    internal fun isBottomAnimating() = PropertyAnimator.isAnimating(targetView, bottomAnimatable)
+
+    /** Set the current top roundness */
+    internal fun setTopRoundness(
+        value: Float,
+        animated: Boolean = targetView.isShown,
+    ) {
+        PropertyAnimator.setProperty(targetView, topAnimatable, value, DURATION, animated)
+    }
+
+    /** Set the current bottom roundness */
+    internal fun setBottomRoundness(
+        value: Float,
+        animated: Boolean = targetView.isShown,
+    ) {
+        PropertyAnimator.setProperty(targetView, bottomAnimatable, value, DURATION, animated)
+    }
+
+    companion object {
+        private val DURATION: AnimationProperties =
+            AnimationProperties()
+                .setDuration(StackStateAnimator.ANIMATION_DURATION_CORNER_RADIUS.toLong())
+
+        private fun topAnimatable(roundable: Roundable): AnimatableProperty =
+            AnimatableProperty.from(
+                object : FloatProperty<View>("topRoundness") {
+                    override fun get(view: View): Float = roundable.topRoundness
+
+                    override fun setValue(view: View, value: Float) {
+                        roundable.roundableState.topRoundness = value
+                        roundable.applyRoundness()
+                    }
+                },
+                R.id.top_roundess_animator_tag,
+                R.id.top_roundess_animator_end_tag,
+                R.id.top_roundess_animator_start_tag,
+            )
+
+        private fun bottomAnimatable(roundable: Roundable): AnimatableProperty =
+            AnimatableProperty.from(
+                object : FloatProperty<View>("bottomRoundness") {
+                    override fun get(view: View): Float = roundable.bottomRoundness
+
+                    override fun setValue(view: View, value: Float) {
+                        roundable.roundableState.bottomRoundness = value
+                        roundable.applyRoundness()
+                    }
+                },
+                R.id.bottom_roundess_animator_tag,
+                R.id.bottom_roundess_animator_end_tag,
+                R.id.bottom_roundess_animator_start_tag,
+            )
+    }
+}
+
+enum class SourceType {
+    DefaultValue,
+    OnDismissAnimation,
+    OnScroll,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
index f8449ae..84ab0d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
@@ -68,6 +68,9 @@
      */
     var stableIndex: Int = -1
 
+    /** Access the index of the [section] or -1 if the entry does not have one */
+    val sectionIndex: Int get() = section?.index ?: -1
+
     /** Copies the state of another instance. */
     fun clone(other: ListAttachState) {
         parent = other.parent
@@ -95,11 +98,13 @@
      * This can happen if the entry is removed from a group that was broken up or if the entry was
      * filtered out during any of the filtering steps.
      */
-    fun detach() {
+    fun detach(includingStableIndex: Boolean) {
         parent = null
         section = null
         promoter = null
-        // stableIndex = -1  // TODO(b/241229236): Clear this once we fix the stability fragility
+        if (includingStableIndex) {
+            stableIndex = -1
+        }
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 2887f97..df35c9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -602,7 +602,7 @@
 
         mInconsistencyTracker.logNewMissingNotifications(rankingMap);
         mInconsistencyTracker.logNewInconsistentRankings(currentEntriesWithoutRankings, rankingMap);
-        if (currentEntriesWithoutRankings != null && mNotifPipelineFlags.removeUnrankedNotifs()) {
+        if (currentEntriesWithoutRankings != null) {
             for (NotificationEntry entry : currentEntriesWithoutRankings.values()) {
                 entry.mCancellationReason = REASON_UNKNOWN;
                 tryRemoveNotification(entry);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index e129ee4..3ae2545 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -54,6 +54,9 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState;
+import com.android.systemui.statusbar.notification.collection.listbuilder.SemiStableSort;
+import com.android.systemui.statusbar.notification.collection.listbuilder.SemiStableSort.StableOrder;
+import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderHelper;
 import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.DefaultNotifStabilityManager;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator;
@@ -96,11 +99,14 @@
     // used exclusivly by ShadeListBuilder#notifySectionEntriesUpdated
     // TODO replace temp with collection pool for readability
     private final ArrayList<ListEntry> mTempSectionMembers = new ArrayList<>();
+    private NotifPipelineFlags mFlags;
     private final boolean mAlwaysLogList;
 
     private List<ListEntry> mNotifList = new ArrayList<>();
     private List<ListEntry> mNewNotifList = new ArrayList<>();
 
+    private final SemiStableSort mSemiStableSort = new SemiStableSort();
+    private final StableOrder<ListEntry> mStableOrder = this::getStableOrderRank;
     private final PipelineState mPipelineState = new PipelineState();
     private final Map<String, GroupEntry> mGroups = new ArrayMap<>();
     private Collection<NotificationEntry> mAllEntries = Collections.emptyList();
@@ -141,6 +147,7 @@
     ) {
         mSystemClock = systemClock;
         mLogger = logger;
+        mFlags = flags;
         mAlwaysLogList = flags.isDevLoggingEnabled();
         mInteractionTracker = interactionTracker;
         mChoreographer = pipelineChoreographer;
@@ -527,7 +534,7 @@
             List<NotifFilter> filters) {
         Trace.beginSection("ShadeListBuilder.filterNotifs");
         final long now = mSystemClock.uptimeMillis();
-        for (ListEntry entry : entries)  {
+        for (ListEntry entry : entries) {
             if (entry instanceof GroupEntry) {
                 final GroupEntry groupEntry = (GroupEntry) entry;
 
@@ -958,7 +965,8 @@
      * filtered out during any of the filtering steps.
      */
     private void annulAddition(ListEntry entry) {
-        entry.getAttachState().detach();
+        // NOTE(b/241229236): Don't clear stableIndex until we fix stability fragility
+        entry.getAttachState().detach(/* includingStableIndex= */ mFlags.isSemiStableSortEnabled());
     }
 
     private void assignSections() {
@@ -978,7 +986,16 @@
 
     private void sortListAndGroups() {
         Trace.beginSection("ShadeListBuilder.sortListAndGroups");
-        // Assign sections to top-level elements and sort their children
+        if (mFlags.isSemiStableSortEnabled()) {
+            sortWithSemiStableSort();
+        } else {
+            sortWithLegacyStability();
+        }
+        Trace.endSection();
+    }
+
+    private void sortWithLegacyStability() {
+        // Sort all groups and the top level list
         for (ListEntry entry : mNotifList) {
             if (entry instanceof GroupEntry) {
                 GroupEntry parent = (GroupEntry) entry;
@@ -991,16 +1008,15 @@
         // Check for suppressed order changes
         if (!getStabilityManager().isEveryChangeAllowed()) {
             mForceReorderable = true;
-            boolean isSorted = isShadeSorted();
+            boolean isSorted = isShadeSortedLegacy();
             mForceReorderable = false;
             if (!isSorted) {
                 getStabilityManager().onEntryReorderSuppressed();
             }
         }
-        Trace.endSection();
     }
 
-    private boolean isShadeSorted() {
+    private boolean isShadeSortedLegacy() {
         if (!isSorted(mNotifList, mTopLevelComparator)) {
             return false;
         }
@@ -1014,6 +1030,43 @@
         return true;
     }
 
+    private void sortWithSemiStableSort() {
+        // Sort each group's children
+        boolean allSorted = true;
+        for (ListEntry entry : mNotifList) {
+            if (entry instanceof GroupEntry) {
+                GroupEntry parent = (GroupEntry) entry;
+                allSorted &= sortGroupChildren(parent.getRawChildren());
+            }
+        }
+        // Sort each section within the top level list
+        mNotifList.sort(mTopLevelComparator);
+        if (!getStabilityManager().isEveryChangeAllowed()) {
+            for (List<ListEntry> subList : getSectionSubLists(mNotifList)) {
+                allSorted &= mSemiStableSort.stabilizeTo(subList, mStableOrder, mNewNotifList);
+            }
+            applyNewNotifList();
+        }
+        assignIndexes(mNotifList);
+        if (!allSorted) {
+            // Report suppressed order changes
+            getStabilityManager().onEntryReorderSuppressed();
+        }
+    }
+
+    private Iterable<List<ListEntry>> getSectionSubLists(List<ListEntry> entries) {
+        return ShadeListBuilderHelper.INSTANCE.getSectionSubLists(entries);
+    }
+
+    private boolean sortGroupChildren(List<NotificationEntry> entries) {
+        if (getStabilityManager().isEveryChangeAllowed()) {
+            entries.sort(mGroupChildrenComparator);
+            return true;
+        } else {
+            return mSemiStableSort.sort(entries, mStableOrder, mGroupChildrenComparator);
+        }
+    }
+
     /** Determine whether the items in the list are sorted according to the comparator */
     @VisibleForTesting
     public static <T> boolean isSorted(List<T> items, Comparator<? super T> comparator) {
@@ -1036,27 +1089,41 @@
     /**
      * Assign the index of each notification relative to the total order
      */
-    private static void assignIndexes(List<ListEntry> notifList) {
+    private void assignIndexes(List<ListEntry> notifList) {
         if (notifList.size() == 0) return;
         NotifSection currentSection = requireNonNull(notifList.get(0).getSection());
         int sectionMemberIndex = 0;
         for (int i = 0; i < notifList.size(); i++) {
-            ListEntry entry = notifList.get(i);
+            final ListEntry entry = notifList.get(i);
             NotifSection section = requireNonNull(entry.getSection());
             if (section.getIndex() != currentSection.getIndex()) {
                 sectionMemberIndex = 0;
                 currentSection = section;
             }
-            entry.getAttachState().setStableIndex(sectionMemberIndex);
-            if (entry instanceof GroupEntry) {
-                GroupEntry parent = (GroupEntry) entry;
-                for (int j = 0; j < parent.getChildren().size(); j++) {
-                    entry = parent.getChildren().get(j);
-                    entry.getAttachState().setStableIndex(sectionMemberIndex);
-                    sectionMemberIndex++;
+            if (mFlags.isStabilityIndexFixEnabled()) {
+                entry.getAttachState().setStableIndex(sectionMemberIndex++);
+                if (entry instanceof GroupEntry) {
+                    final GroupEntry parent = (GroupEntry) entry;
+                    final NotificationEntry summary = parent.getSummary();
+                    if (summary != null) {
+                        summary.getAttachState().setStableIndex(sectionMemberIndex++);
+                    }
+                    for (NotificationEntry child : parent.getChildren()) {
+                        child.getAttachState().setStableIndex(sectionMemberIndex++);
+                    }
                 }
+            } else {
+                // This old implementation uses the same index number for the group as the first
+                // child, and fails to assign an index to the summary.  Remove once tested.
+                entry.getAttachState().setStableIndex(sectionMemberIndex);
+                if (entry instanceof GroupEntry) {
+                    final GroupEntry parent = (GroupEntry) entry;
+                    for (NotificationEntry child : parent.getChildren()) {
+                        child.getAttachState().setStableIndex(sectionMemberIndex++);
+                    }
+                }
+                sectionMemberIndex++;
             }
-            sectionMemberIndex++;
         }
     }
 
@@ -1196,7 +1263,7 @@
                 o2.getSectionIndex());
         if (cmp != 0) return cmp;
 
-        cmp = Integer.compare(
+        cmp = mFlags.isSemiStableSortEnabled() ? 0 : Integer.compare(
                 getStableOrderIndex(o1),
                 getStableOrderIndex(o2));
         if (cmp != 0) return cmp;
@@ -1225,7 +1292,7 @@
 
 
     private final Comparator<NotificationEntry> mGroupChildrenComparator = (o1, o2) -> {
-        int cmp = Integer.compare(
+        int cmp = mFlags.isSemiStableSortEnabled() ? 0 : Integer.compare(
                 getStableOrderIndex(o1),
                 getStableOrderIndex(o2));
         if (cmp != 0) return cmp;
@@ -1256,9 +1323,25 @@
             // let the stability manager constrain or allow reordering
             return -1;
         }
+        // NOTE(b/241229236): Can't use cleared section index until we fix stability fragility
         return entry.getPreviousAttachState().getStableIndex();
     }
 
+    @Nullable
+    private Integer getStableOrderRank(ListEntry entry) {
+        if (getStabilityManager().isEntryReorderingAllowed(entry)) {
+            // let the stability manager constrain or allow reordering
+            return null;
+        }
+        if (entry.getAttachState().getSectionIndex()
+                != entry.getPreviousAttachState().getSectionIndex()) {
+            // stable index is only valid within the same section; otherwise we allow reordering
+            return null;
+        }
+        final int stableIndex = entry.getPreviousAttachState().getStableIndex();
+        return stableIndex == -1 ? null : stableIndex;
+    }
+
     private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
         final NotifFilter filter = findRejectingFilter(entry, now, filters);
         entry.getAttachState().setExcludingFilter(filter);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
index 211e374..68d1319 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.notification.collection.coalescer
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
 import javax.inject.Inject
 
 class GroupCoalescerLogger @Inject constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
index e8f352f..2919def 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
@@ -1,8 +1,8 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
 import com.android.systemui.statusbar.notification.row.NotificationGuts
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 8f3eb4f..8a31ed9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -18,6 +18,8 @@
 import android.app.Notification
 import android.app.Notification.GROUP_ALERT_SUMMARY
 import android.util.ArrayMap
+import android.util.ArraySet
+import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.NotificationRemoteInputManager
 import com.android.systemui.statusbar.notification.collection.GroupEntry
@@ -70,6 +72,7 @@
     @Main private val mExecutor: DelayableExecutor,
 ) : Coordinator {
     private val mEntriesBindingUntil = ArrayMap<String, Long>()
+    private val mEntriesUpdateTimes = ArrayMap<String, Long>()
     private var mEndLifetimeExtension: OnEndLifetimeExtensionCallback? = null
     private lateinit var mNotifPipeline: NotifPipeline
     private var mNow: Long = -1
@@ -264,6 +267,9 @@
         }
         // After this method runs, all posted entries should have been handled (or skipped).
         mPostedEntries.clear()
+
+        // Also take this opportunity to clean up any stale entry update times
+        cleanUpEntryUpdateTimes()
     }
 
     /**
@@ -378,6 +384,9 @@
                 isAlerting = false,
                 isBinding = false,
             )
+
+            // Record the last updated time for this key
+            setUpdateTime(entry, mSystemClock.currentTimeMillis())
         }
 
         /**
@@ -419,6 +428,9 @@
                     cancelHeadsUpBind(posted.entry)
                 }
             }
+
+            // Update last updated time for this entry
+            setUpdateTime(entry, mSystemClock.currentTimeMillis())
         }
 
         /**
@@ -426,6 +438,7 @@
          */
         override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
             mPostedEntries.remove(entry.key)
+            mEntriesUpdateTimes.remove(entry.key)
             cancelHeadsUpBind(entry)
             val entryKey = entry.key
             if (mHeadsUpManager.isAlerting(entryKey)) {
@@ -454,7 +467,12 @@
             // never) in mPostedEntries to need to alert, we need to check every notification
             // known to the pipeline.
             for (entry in mNotifPipeline.allNotifs) {
-                // The only entries we can consider alerting for here are entries that have never
+                // Only consider entries that are recent enough, since we want to apply a fairly
+                // strict threshold for when an entry should be updated via only ranking and not an
+                // app-provided notification update.
+                if (!isNewEnoughForRankingUpdate(entry)) continue
+
+                // The only entries we consider alerting for here are entries that have never
                 // interrupted and that now say they should heads up; if they've alerted in the
                 // past, we don't want to incorrectly alert a second time if there wasn't an
                 // explicit notification update.
@@ -486,6 +504,41 @@
                 (entry.sbn.notification.flags and Notification.FLAG_ONLY_ALERT_ONCE) == 0)
     }
 
+    /**
+     * Sets the updated time for the given entry to the specified time.
+     */
+    @VisibleForTesting
+    fun setUpdateTime(entry: NotificationEntry, time: Long) {
+        mEntriesUpdateTimes[entry.key] = time
+    }
+
+    /**
+     * Checks whether the entry is new enough to be updated via ranking update.
+     * We want to avoid updating an entry too long after it was originally posted/updated when we're
+     * only reacting to a ranking change, as relevant ranking updates are expected to come in
+     * fairly soon after the posting of a notification.
+     */
+    private fun isNewEnoughForRankingUpdate(entry: NotificationEntry): Boolean {
+        // If we don't have an update time for this key, default to "too old"
+        if (!mEntriesUpdateTimes.containsKey(entry.key)) return false
+
+        val updateTime = mEntriesUpdateTimes[entry.key] ?: return false
+        return (mSystemClock.currentTimeMillis() - updateTime) <= MAX_RANKING_UPDATE_DELAY_MS
+    }
+
+    private fun cleanUpEntryUpdateTimes() {
+        // Because we won't update entries that are older than this amount of time anyway, clean
+        // up any entries that are too old to notify.
+        val toRemove = ArraySet<String>()
+        for ((key, updateTime) in mEntriesUpdateTimes) {
+            if (updateTime == null ||
+                    (mSystemClock.currentTimeMillis() - updateTime) > MAX_RANKING_UPDATE_DELAY_MS) {
+                toRemove.add(key)
+            }
+        }
+        mEntriesUpdateTimes.removeAll(toRemove)
+    }
+
     /** When an action is pressed on a notification, end HeadsUp lifetime extension. */
     private val mActionPressListener = Consumer<NotificationEntry> { entry ->
         if (mNotifsExtendingLifetime.contains(entry)) {
@@ -597,6 +650,9 @@
     companion object {
         private const val TAG = "HeadsUpCoordinator"
         private const val BIND_TIMEOUT = 1000L
+
+        // This value is set to match MAX_SOUND_DELAY_MS in NotificationRecord.
+        private const val MAX_RANKING_UPDATE_DELAY_MS: Long = 2000
     }
 
     data class PostedEntry(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
index 8625cdb..dfaa291 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
@@ -1,9 +1,10 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import android.util.Log
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
 import javax.inject.Inject
 
 private const val TAG = "HeadsUpCoordinator"
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 2480ff6..0be4bde 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,14 +16,14 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
-import static com.android.systemui.media.MediaDataManagerKt.isMediaNotification;
+import static com.android.systemui.media.controls.pipeline.MediaDataManagerKt.isMediaNotification;
 
 import android.os.RemoteException;
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
 
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.media.MediaFeatureFlag;
+import com.android.systemui.media.controls.util.MediaFeatureFlag;
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 6e76691..d2db622 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -407,7 +407,10 @@
             mLogger.logGroupInflationTookTooLong(group);
             return false;
         }
-        if (mInflatingNotifs.contains(group.getSummary())) {
+        // Only delay release if the summary is not inflated.
+        // TODO(253454977): Once we ensure that all other pipeline filtering and pruning has been
+        //  done by this point, we can revert back to checking for mInflatingNotifs.contains(...)
+        if (group.getSummary() != null && !isInflated(group.getSummary())) {
             mLogger.logDelayingGroupRelease(group, group.getSummary());
             return true;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
index c4f4ed5..9558f47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
index c687e1b..d804454 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
 import javax.inject.Inject
 
 private const val TAG = "ShadeEventCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSort.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSort.kt
new file mode 100644
index 0000000..9ec8e07
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSort.kt
@@ -0,0 +1,200 @@
+/*
+ * 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.statusbar.notification.collection.listbuilder
+
+import androidx.annotation.VisibleForTesting
+import kotlin.math.sign
+
+class SemiStableSort {
+    val preallocatedWorkspace by lazy { ArrayList<Any>() }
+    val preallocatedAdditions by lazy { ArrayList<Any>() }
+    val preallocatedMapToIndex by lazy { HashMap<Any, Int>() }
+    val preallocatedMapToIndexComparator: Comparator<Any> by lazy {
+        Comparator.comparingInt { item -> preallocatedMapToIndex[item] ?: -1 }
+    }
+
+    /**
+     * Sort the given [items] such that items which have a [stableOrder] will all be in that order,
+     * items without a [stableOrder] will be sorted according to the comparator, and the two sets of
+     * items will be combined to have the fewest elements out of order according to the [comparator]
+     * . The result will be placed into the original [items] list.
+     */
+    fun <T : Any> sort(
+        items: MutableList<T>,
+        stableOrder: StableOrder<in T>,
+        comparator: Comparator<in T>,
+    ): Boolean =
+        withWorkspace<T, Boolean> { workspace ->
+            val ordered =
+                sortTo(
+                    items,
+                    stableOrder,
+                    comparator,
+                    workspace,
+                )
+            items.clear()
+            items.addAll(workspace)
+            return ordered
+        }
+
+    /**
+     * Sort the given [items] such that items which have a [stableOrder] will all be in that order,
+     * items without a [stableOrder] will be sorted according to the comparator, and the two sets of
+     * items will be combined to have the fewest elements out of order according to the [comparator]
+     * . The result will be put into [output].
+     */
+    fun <T : Any> sortTo(
+        items: Iterable<T>,
+        stableOrder: StableOrder<in T>,
+        comparator: Comparator<in T>,
+        output: MutableList<T>,
+    ): Boolean {
+        if (DEBUG) println("\n> START from ${items.map { it to stableOrder.getRank(it) }}")
+        // If array already has elements, use subList to ensure we only append
+        val result = output.takeIf { it.isEmpty() } ?: output.subList(output.size, output.size)
+        items.filterTo(result) { stableOrder.getRank(it) != null }
+        result.sortBy { stableOrder.getRank(it)!! }
+        val isOrdered = result.isSorted(comparator)
+        withAdditions<T> { additions ->
+            items.filterTo(additions) { stableOrder.getRank(it) == null }
+            additions.sortWith(comparator)
+            insertPreSortedElementsWithFewestMisOrderings(result, additions, comparator)
+        }
+        return isOrdered
+    }
+
+    /**
+     * Rearrange the [sortedItems] to enforce that items are in the [stableOrder], and store the
+     * result in [output]. Items with a [stableOrder] will be in that order, items without a
+     * [stableOrder] will remain in same relative order as the input, and the two sets of items will
+     * be combined to have the fewest elements moved from their locations in the original.
+     */
+    fun <T : Any> stabilizeTo(
+        sortedItems: Iterable<T>,
+        stableOrder: StableOrder<in T>,
+        output: MutableList<T>,
+    ): Boolean {
+        // Append to the output array if present
+        val result = output.takeIf { it.isEmpty() } ?: output.subList(output.size, output.size)
+        sortedItems.filterTo(result) { stableOrder.getRank(it) != null }
+        val stableRankComparator = compareBy<T> { stableOrder.getRank(it)!! }
+        val isOrdered = result.isSorted(stableRankComparator)
+        if (!isOrdered) {
+            result.sortWith(stableRankComparator)
+        }
+        if (result.isEmpty()) {
+            sortedItems.filterTo(result) { stableOrder.getRank(it) == null }
+            return isOrdered
+        }
+        withAdditions<T> { additions ->
+            sortedItems.filterTo(additions) { stableOrder.getRank(it) == null }
+            if (additions.isNotEmpty()) {
+                withIndexOfComparator(sortedItems) { comparator ->
+                    insertPreSortedElementsWithFewestMisOrderings(result, additions, comparator)
+                }
+            }
+        }
+        return isOrdered
+    }
+
+    private inline fun <T : Any, R> withWorkspace(block: (ArrayList<T>) -> R): R {
+        preallocatedWorkspace.clear()
+        val result = block(preallocatedWorkspace as ArrayList<T>)
+        preallocatedWorkspace.clear()
+        return result
+    }
+
+    private inline fun <T : Any> withAdditions(block: (ArrayList<T>) -> Unit) {
+        preallocatedAdditions.clear()
+        block(preallocatedAdditions as ArrayList<T>)
+        preallocatedAdditions.clear()
+    }
+
+    private inline fun <T : Any> withIndexOfComparator(
+        sortedItems: Iterable<T>,
+        block: (Comparator<in T>) -> Unit
+    ) {
+        preallocatedMapToIndex.clear()
+        sortedItems.forEachIndexed { i, item -> preallocatedMapToIndex[item] = i }
+        block(preallocatedMapToIndexComparator as Comparator<in T>)
+        preallocatedMapToIndex.clear()
+    }
+
+    companion object {
+
+        /**
+         * This is the core of the algorithm.
+         *
+         * Insert [preSortedAdditions] (the elements to be inserted) into [existing] without
+         * changing the relative order of any elements already in [existing], even though those
+         * elements may be mis-ordered relative to the [comparator], such that the total number of
+         * elements which are ordered incorrectly according to the [comparator] is fewest.
+         */
+        private fun <T> insertPreSortedElementsWithFewestMisOrderings(
+            existing: MutableList<T>,
+            preSortedAdditions: Iterable<T>,
+            comparator: Comparator<in T>,
+        ) {
+            if (DEBUG) println("  To $existing insert $preSortedAdditions with fewest misordering")
+            var iStart = 0
+            preSortedAdditions.forEach { toAdd ->
+                if (DEBUG) println("    need to add $toAdd to $existing, starting at $iStart")
+                var cmpSum = 0
+                var cmpSumMax = 0
+                var iCmpSumMax = iStart
+                if (DEBUG) print("      ")
+                for (i in iCmpSumMax until existing.size) {
+                    val cmp = comparator.compare(toAdd, existing[i]).sign
+                    cmpSum += cmp
+                    if (cmpSum > cmpSumMax) {
+                        cmpSumMax = cmpSum
+                        iCmpSumMax = i + 1
+                    }
+                    if (DEBUG) print("sum[$i]=$cmpSum, ")
+                }
+                if (DEBUG) println("inserting $toAdd at $iCmpSumMax")
+                existing.add(iCmpSumMax, toAdd)
+                iStart = iCmpSumMax + 1
+            }
+        }
+
+        /** Determines if a list is correctly sorted according to the given comparator */
+        @VisibleForTesting
+        fun <T> List<T>.isSorted(comparator: Comparator<T>): Boolean {
+            if (this.size <= 1) {
+                return true
+            }
+            val iterator = this.iterator()
+            var previous = iterator.next()
+            var current: T?
+            while (iterator.hasNext()) {
+                current = iterator.next()
+                if (comparator.compare(previous, current) > 0) {
+                    return false
+                }
+                previous = current
+            }
+            return true
+        }
+    }
+
+    fun interface StableOrder<T> {
+        fun getRank(item: T): Int?
+    }
+}
+
+val DEBUG = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt
new file mode 100644
index 0000000..d8f75f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.statusbar.notification.collection.listbuilder
+
+import com.android.systemui.statusbar.notification.collection.ListEntry
+
+object ShadeListBuilderHelper {
+    fun getSectionSubLists(entries: List<ListEntry>): Iterable<List<ListEntry>> =
+        getContiguousSubLists(entries, minLength = 1) { it.sectionIndex }
+
+    inline fun <T : Any, K : Any> getContiguousSubLists(
+        itemList: List<T>,
+        minLength: Int = 1,
+        key: (T) -> K,
+    ): Iterable<List<T>> {
+        val subLists = mutableListOf<List<T>>()
+        val numEntries = itemList.size
+        var currentSectionStartIndex = 0
+        var currentSectionKey: K? = null
+        for (i in 0 until numEntries) {
+            val sectionKey = key(itemList[i])
+            if (currentSectionKey == null) {
+                currentSectionKey = sectionKey
+            } else if (currentSectionKey != sectionKey) {
+                val length = i - currentSectionStartIndex
+                if (length >= minLength) {
+                    subLists.add(itemList.subList(currentSectionStartIndex, i))
+                }
+                currentSectionStartIndex = i
+                currentSectionKey = sectionKey
+            }
+        }
+        val length = numEntries - currentSectionStartIndex
+        if (length >= minLength) {
+            subLists.add(itemList.subList(currentSectionStartIndex, numEntries))
+        }
+        return subLists
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index d8dae5d..8e052c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -16,11 +16,11 @@
 
 package com.android.systemui.statusbar.notification.collection.listbuilder
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
 import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogLevel.WARNING
 import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.ListEntry
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index aa27e1e..911a2d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -20,13 +20,13 @@
 import android.service.notification.NotificationListenerService
 import android.service.notification.NotificationListenerService.RankingMap
 import android.service.notification.StatusBarNotification
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
-import com.android.systemui.log.LogLevel.WTF
 import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.ERROR
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogLevel.WARNING
+import com.android.systemui.plugins.log.LogLevel.WTF
 import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason
 import com.android.systemui.statusbar.notification.collection.NotifCollection.FutureDismissal
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
index 38e3d49..9c71e5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.notification.collection.render
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
 import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
 import com.android.systemui.util.Compile
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
index b6278d1..fde4ecb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.collection.render
 
+import javax.inject.Inject
+
 /** An interface by which the pipeline can make updates to the notification root view. */
 interface NotifStackController {
     /** Provides stats about the list of notifications attached to the shade */
@@ -42,6 +44,6 @@
  * methods, rather than forcing us to add no-op implementations in their implementation every time
  * a method is added.
  */
-open class DefaultNotifStackController : NotifStackController {
+open class DefaultNotifStackController @Inject constructor() : NotifStackController {
     override fun setNotifStats(stats: NotifStats) {}
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index 6d1071c..b4b9438 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.notification.collection.render
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
 import java.lang.RuntimeException
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
index b2cb23b..a5278c3d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.phone.CentralSurfaces
 
 /**
  * The master controller for all notifications-related work
@@ -32,6 +33,7 @@
  */
 interface NotificationsController {
     fun initialize(
+        centralSurfaces: CentralSurfaces,
         presenter: NotificationPresenter,
         listContainer: NotificationListContainer,
         stackController: NotifStackController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 8e646a3..8eef3f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
 import com.android.systemui.statusbar.NotificationListener
+import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.statusbar.NotificationPresenter
 import com.android.systemui.statusbar.notification.AnimatedImageNotificationManager
 import com.android.systemui.statusbar.notification.NotificationActivityStarter
@@ -38,6 +39,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
+import com.android.systemui.statusbar.notification.logging.NotificationLogger
 import com.android.systemui.statusbar.notification.logging.NotificationMemoryMonitor
 import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
@@ -56,7 +58,6 @@
  */
 @SysUISingleton
 class NotificationsControllerImpl @Inject constructor(
-    private val centralSurfaces: Lazy<CentralSurfaces>,
     private val notificationListener: NotificationListener,
     private val commonNotifCollection: Lazy<CommonNotifCollection>,
     private val notifPipeline: Lazy<NotifPipeline>,
@@ -64,7 +65,9 @@
     private val targetSdkResolver: TargetSdkResolver,
     private val notifPipelineInitializer: Lazy<NotifPipelineInitializer>,
     private val notifBindPipelineInitializer: NotifBindPipelineInitializer,
+    private val notificationLogger: NotificationLogger,
     private val notificationRowBinder: NotificationRowBinderImpl,
+    private val notificationsMediaManager: NotificationMediaManager,
     private val headsUpViewBinder: HeadsUpViewBinder,
     private val clickerBuilder: NotificationClicker.Builder,
     private val animatedImageNotificationManager: AnimatedImageNotificationManager,
@@ -76,6 +79,7 @@
 ) : NotificationsController {
 
     override fun initialize(
+        centralSurfaces: CentralSurfaces,
         presenter: NotificationPresenter,
         listContainer: NotificationListContainer,
         stackController: NotifStackController,
@@ -92,8 +96,8 @@
 
         notificationRowBinder.setNotificationClicker(
                 clickerBuilder.build(
-                    Optional.of(
-                        centralSurfaces.get()), bubblesOptional, notificationActivityStarter))
+                    Optional.ofNullable(centralSurfaces), bubblesOptional,
+                        notificationActivityStarter))
         notificationRowBinder.setUpWithPresenter(
                 presenter,
                 listContainer,
@@ -109,7 +113,8 @@
                 stackController)
 
         targetSdkResolver.initialize(notifPipeline.get())
-
+        notificationsMediaManager.setUpWithPresenter(presenter)
+        notificationLogger.setUpWithContainer(listContainer)
         peopleSpaceWidgetManager.attach(notificationListener)
         fgsNotifListener.init()
         if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_MONITOR_ENABLED)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
index 744166d..14856da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.phone.CentralSurfaces
 import javax.inject.Inject
 
 /**
@@ -34,6 +35,7 @@
 ) : NotificationsController {
 
     override fun initialize(
+        centralSurfaces: CentralSurfaces,
         presenter: NotificationPresenter,
         listContainer: NotificationListContainer,
         stackController: NotifStackController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
index 5dbec8d..d4f11fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
@@ -1,8 +1,8 @@
 package com.android.systemui.statusbar.notification.interruption
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.INFO
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index 659df24..e6dbcee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -231,7 +231,7 @@
     private fun readShowSilentNotificationSetting() {
         val showSilentNotifs =
                 secureSettings.getBoolForUser(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
-                        true, UserHandle.USER_CURRENT)
+                        false, UserHandle.USER_CURRENT)
         hideSilentNotificationsOnLockscreen = !showSilentNotifs
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 99d320d..073b6b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -16,11 +16,11 @@
 
 package com.android.systemui.statusbar.notification.interruption
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
 import com.android.systemui.log.dagger.NotificationInterruptLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogLevel.WARNING
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index c5a6921..c4f5a3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -17,6 +17,8 @@
 package com.android.systemui.statusbar.notification.interruption;
 
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD;
+import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR;
 
 import android.app.NotificationManager;
 import android.content.ContentResolver;
@@ -32,6 +34,8 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -68,10 +72,30 @@
     private final NotificationInterruptLogger mLogger;
     private final NotifPipelineFlags mFlags;
     private final KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
+    private final UiEventLogger mUiEventLogger;
 
     @VisibleForTesting
     protected boolean mUseHeadsUp = false;
 
+    public enum NotificationInterruptEvent implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "FSI suppressed for suppressive GroupAlertBehavior")
+        FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(1235),
+
+        @UiEvent(doc = "FSI suppressed for requiring neither HUN nor keyguard")
+        FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD(1236);
+
+        private final int mId;
+
+        NotificationInterruptEvent(int id) {
+            mId = id;
+        }
+
+        @Override
+        public int getId() {
+            return mId;
+        }
+    }
+
     @Inject
     public NotificationInterruptStateProviderImpl(
             ContentResolver contentResolver,
@@ -85,7 +109,8 @@
             NotificationInterruptLogger logger,
             @Main Handler mainHandler,
             NotifPipelineFlags flags,
-            KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider) {
+            KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
+            UiEventLogger uiEventLogger) {
         mContentResolver = contentResolver;
         mPowerManager = powerManager;
         mDreamManager = dreamManager;
@@ -97,6 +122,7 @@
         mLogger = logger;
         mFlags = flags;
         mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider;
+        mUiEventLogger = uiEventLogger;
         ContentObserver headsUpObserver = new ContentObserver(mainHandler) {
             @Override
             public void onChange(boolean selfChange) {
@@ -203,7 +229,9 @@
             // b/231322873: Detect and report an event when a notification has both an FSI and a
             // suppressive groupAlertBehavior, and now correctly block the FSI from firing.
             final int uid = entry.getSbn().getUid();
+            final String packageName = entry.getSbn().getPackageName();
             android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "groupAlertBehavior");
+            mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, uid, packageName);
             mLogger.logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
             return false;
         }
@@ -249,7 +277,9 @@
             // Detect the case determined by b/231322873 to launch FSI while device is in use,
             // as blocked by the correct implementation, and report the event.
             final int uid = entry.getSbn().getUid();
+            final String packageName = entry.getSbn().getPackageName();
             android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "no hun or keyguard");
+            mUiEventLogger.log(FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, uid, packageName);
             mLogger.logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
             return false;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
index 832a739..0380fff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
@@ -20,8 +20,9 @@
 /** Describes usage of a notification. */
 data class NotificationMemoryUsage(
     val packageName: String,
-    val notificationId: String,
+    val notificationKey: String,
     val objectUsage: NotificationObjectUsage,
+    val viewUsage: List<NotificationViewUsage>
 )
 
 /**
@@ -39,3 +40,26 @@
     val extender: Int,
     val hasCustomView: Boolean,
 )
+
+enum class ViewType {
+    PUBLIC_VIEW,
+    PRIVATE_CONTRACTED_VIEW,
+    PRIVATE_EXPANDED_VIEW,
+    PRIVATE_HEADS_UP_VIEW,
+    TOTAL
+}
+
+/**
+ * Describes current memory of a notification view hierarchy.
+ *
+ * The values are in bytes.
+ */
+data class NotificationViewUsage(
+    val viewType: ViewType,
+    val smallIcon: Int,
+    val largeIcon: Int,
+    val systemIcons: Int,
+    val style: Int,
+    val customViews: Int,
+    val softwareBitmapsPenalty: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt
new file mode 100644
index 0000000..7d39e18
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt
@@ -0,0 +1,212 @@
+package com.android.systemui.statusbar.notification.logging
+
+import android.app.Notification
+import android.app.Person
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.annotation.WorkerThread
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+
+/** Calculates estimated memory usage of [Notification] and [NotificationEntry] objects. */
+internal object NotificationMemoryMeter {
+
+    private const val CAR_EXTENSIONS = "android.car.EXTENSIONS"
+    private const val CAR_EXTENSIONS_LARGE_ICON = "large_icon"
+    private const val TV_EXTENSIONS = "android.tv.EXTENSIONS"
+    private const val WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"
+    private const val WEARABLE_EXTENSIONS_BACKGROUND = "background"
+
+    /** Returns a list of memory use entries for currently shown notifications. */
+    @WorkerThread
+    fun notificationMemoryUse(
+        notifications: Collection<NotificationEntry>,
+    ): List<NotificationMemoryUsage> {
+        return notifications
+            .asSequence()
+            .map { entry ->
+                val packageName = entry.sbn.packageName
+                val notificationObjectUsage =
+                    notificationMemoryUse(entry.sbn.notification, hashSetOf())
+                val notificationViewUsage = NotificationMemoryViewWalker.getViewUsage(entry.row)
+                NotificationMemoryUsage(
+                    packageName,
+                    NotificationUtils.logKey(entry.sbn.key),
+                    notificationObjectUsage,
+                    notificationViewUsage
+                )
+            }
+            .toList()
+    }
+
+    @WorkerThread
+    fun notificationMemoryUse(
+        entry: NotificationEntry,
+        seenBitmaps: HashSet<Int> = hashSetOf(),
+    ): NotificationMemoryUsage {
+        return NotificationMemoryUsage(
+            entry.sbn.packageName,
+            NotificationUtils.logKey(entry.sbn.key),
+            notificationMemoryUse(entry.sbn.notification, seenBitmaps),
+            NotificationMemoryViewWalker.getViewUsage(entry.row)
+        )
+    }
+
+    /**
+     * Computes the estimated memory usage of a given [Notification] object. It'll attempt to
+     * inspect Bitmaps in the object and provide summary of memory usage.
+     */
+    @WorkerThread
+    fun notificationMemoryUse(
+        notification: Notification,
+        seenBitmaps: HashSet<Int> = hashSetOf(),
+    ): NotificationObjectUsage {
+        val extras = notification.extras
+        val smallIconUse = computeIconUse(notification.smallIcon, seenBitmaps)
+        val largeIconUse = computeIconUse(notification.getLargeIcon(), seenBitmaps)
+
+        // Collect memory usage of extra styles
+
+        // Big Picture
+        val bigPictureIconUse =
+            computeParcelableUse(extras, Notification.EXTRA_LARGE_ICON_BIG, seenBitmaps)
+        val bigPictureUse =
+            computeParcelableUse(extras, Notification.EXTRA_PICTURE, seenBitmaps) +
+                computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps)
+
+        // People
+        val peopleList = extras.getParcelableArrayList<Person>(Notification.EXTRA_PEOPLE_LIST)
+        val peopleUse =
+            peopleList?.sumOf { person -> computeIconUse(person.icon, seenBitmaps) } ?: 0
+
+        // Calling
+        val callingPersonUse =
+            computeParcelableUse(extras, Notification.EXTRA_CALL_PERSON, seenBitmaps)
+        val verificationIconUse =
+            computeParcelableUse(extras, Notification.EXTRA_VERIFICATION_ICON, seenBitmaps)
+
+        // Messages
+        val messages =
+            Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+                extras.getParcelableArray(Notification.EXTRA_MESSAGES)
+            )
+        val messagesUse =
+            messages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
+        val historicMessages =
+            Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+                extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES)
+            )
+        val historyicMessagesUse =
+            historicMessages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
+
+        // Extenders
+        val carExtender = extras.getBundle(CAR_EXTENSIONS)
+        val carExtenderSize = carExtender?.let { computeBundleSize(it) } ?: 0
+        val carExtenderIcon =
+            computeParcelableUse(carExtender, CAR_EXTENSIONS_LARGE_ICON, seenBitmaps)
+
+        val tvExtender = extras.getBundle(TV_EXTENSIONS)
+        val tvExtenderSize = tvExtender?.let { computeBundleSize(it) } ?: 0
+
+        val wearExtender = extras.getBundle(WEARABLE_EXTENSIONS)
+        val wearExtenderSize = wearExtender?.let { computeBundleSize(it) } ?: 0
+        val wearExtenderBackground =
+            computeParcelableUse(wearExtender, WEARABLE_EXTENSIONS_BACKGROUND, seenBitmaps)
+
+        val style = notification.notificationStyle
+        val hasCustomView = notification.contentView != null || notification.bigContentView != null
+        val extrasSize = computeBundleSize(extras)
+
+        return NotificationObjectUsage(
+            smallIcon = smallIconUse,
+            largeIcon = largeIconUse,
+            extras = extrasSize,
+            style = style?.simpleName,
+            styleIcon =
+                bigPictureIconUse +
+                    peopleUse +
+                    callingPersonUse +
+                    verificationIconUse +
+                    messagesUse +
+                    historyicMessagesUse,
+            bigPicture = bigPictureUse,
+            extender =
+                carExtenderSize +
+                    carExtenderIcon +
+                    tvExtenderSize +
+                    wearExtenderSize +
+                    wearExtenderBackground,
+            hasCustomView = hasCustomView
+        )
+    }
+
+    /**
+     * Calculates size of the bundle data (excluding FDs and other shared objects like ashmem
+     * bitmaps). Can be slow.
+     */
+    private fun computeBundleSize(extras: Bundle): Int {
+        val parcel = Parcel.obtain()
+        try {
+            extras.writeToParcel(parcel, 0)
+            return parcel.dataSize()
+        } finally {
+            parcel.recycle()
+        }
+    }
+
+    /**
+     * Deserializes [Icon], [Bitmap] or [Person] from extras and computes its memory use. Returns 0
+     * if the key does not exist in extras.
+     */
+    private fun computeParcelableUse(extras: Bundle?, key: String, seenBitmaps: HashSet<Int>): Int {
+        return when (val parcelable = extras?.getParcelable<Parcelable>(key)) {
+            is Bitmap -> computeBitmapUse(parcelable, seenBitmaps)
+            is Icon -> computeIconUse(parcelable, seenBitmaps)
+            is Person -> computeIconUse(parcelable.icon, seenBitmaps)
+            else -> 0
+        }
+    }
+
+    /**
+     * Calculates the byte size of bitmaps or data in the Icon object. Returns 0 if the icon is
+     * defined via Uri or a resource.
+     *
+     * @return memory usage in bytes or 0 if the icon is Uri/Resource based
+     */
+    private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>) =
+        when (icon?.type) {
+            Icon.TYPE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
+            Icon.TYPE_ADAPTIVE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
+            Icon.TYPE_DATA -> computeDataUse(icon, seenBitmaps)
+            else -> 0
+        }
+
+    /**
+     * Returns the amount of memory a given bitmap is using. If the bitmap reference is part of
+     * seenBitmaps set, this method returns 0 to avoid double counting.
+     *
+     * @return memory usage of the bitmap in bytes
+     */
+    private fun computeBitmapUse(bitmap: Bitmap, seenBitmaps: HashSet<Int>? = null): Int {
+        val refId = System.identityHashCode(bitmap)
+        if (seenBitmaps?.contains(refId) == true) {
+            return 0
+        }
+
+        seenBitmaps?.add(refId)
+        return bitmap.allocationByteCount
+    }
+
+    private fun computeDataUse(icon: Icon, seenBitmaps: HashSet<Int>): Int {
+        val refId = System.identityHashCode(icon.dataBytes)
+        if (seenBitmaps.contains(refId)) {
+            return 0
+        }
+
+        seenBitmaps.add(refId)
+        return icon.dataLength
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
index 958978e..c09cc43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
@@ -17,22 +17,11 @@
 
 package com.android.systemui.statusbar.notification.logging
 
-import android.app.Notification
-import android.app.Person
-import android.graphics.Bitmap
-import android.graphics.drawable.Icon
-import android.os.Bundle
-import android.os.Parcel
-import android.os.Parcelable
 import android.util.Log
-import androidx.annotation.WorkerThread
-import androidx.core.util.contains
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.notification.NotificationUtils
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import java.io.PrintWriter
 import javax.inject.Inject
 
@@ -46,12 +35,7 @@
 ) : Dumpable {
 
     companion object {
-        private const val TAG = "NotificationMemMonitor"
-        private const val CAR_EXTENSIONS = "android.car.EXTENSIONS"
-        private const val CAR_EXTENSIONS_LARGE_ICON = "large_icon"
-        private const val TV_EXTENSIONS = "android.tv.EXTENSIONS"
-        private const val WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"
-        private const val WEARABLE_EXTENSIONS_BACKGROUND = "background"
+        private const val TAG = "NotificationMemory"
     }
 
     fun init() {
@@ -60,184 +44,123 @@
     }
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
-        currentNotificationMemoryUse().forEach { use -> pw.println(use.toString()) }
+        val memoryUse =
+            NotificationMemoryMeter.notificationMemoryUse(notificationPipeline.allNotifs)
+                .sortedWith(compareBy({ it.packageName }, { it.notificationKey }))
+        dumpNotificationObjects(pw, memoryUse)
+        dumpNotificationViewUsage(pw, memoryUse)
     }
 
-    @WorkerThread
-    fun currentNotificationMemoryUse(): List<NotificationMemoryUsage> {
-        return notificationMemoryUse(notificationPipeline.allNotifs)
-    }
-
-    /** Returns a list of memory use entries for currently shown notifications. */
-    @WorkerThread
-    fun notificationMemoryUse(
-        notifications: Collection<NotificationEntry>
-    ): List<NotificationMemoryUsage> {
-        return notifications
-            .asSequence()
-            .map { entry ->
-                val packageName = entry.sbn.packageName
-                val notificationObjectUsage =
-                    computeNotificationObjectUse(entry.sbn.notification, hashSetOf())
-                NotificationMemoryUsage(
-                    packageName,
-                    NotificationUtils.logKey(entry.sbn.key),
-                    notificationObjectUsage
-                )
-            }
-            .toList()
-    }
-
-    /**
-     * Computes the estimated memory usage of a given [Notification] object. It'll attempt to
-     * inspect Bitmaps in the object and provide summary of memory usage.
-     */
-    private fun computeNotificationObjectUse(
-        notification: Notification,
-        seenBitmaps: HashSet<Int>
-    ): NotificationObjectUsage {
-        val extras = notification.extras
-        val smallIconUse = computeIconUse(notification.smallIcon, seenBitmaps)
-        val largeIconUse = computeIconUse(notification.getLargeIcon(), seenBitmaps)
-
-        // Collect memory usage of extra styles
-
-        // Big Picture
-        val bigPictureIconUse =
-            computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps) +
-                computeParcelableUse(extras, Notification.EXTRA_LARGE_ICON_BIG, seenBitmaps)
-        val bigPictureUse =
-            computeParcelableUse(extras, Notification.EXTRA_PICTURE, seenBitmaps) +
-                computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps)
-
-        // People
-        val peopleList = extras.getParcelableArrayList<Person>(Notification.EXTRA_PEOPLE_LIST)
-        val peopleUse =
-            peopleList?.sumOf { person -> computeIconUse(person.icon, seenBitmaps) } ?: 0
-
-        // Calling
-        val callingPersonUse =
-            computeParcelableUse(extras, Notification.EXTRA_CALL_PERSON, seenBitmaps)
-        val verificationIconUse =
-            computeParcelableUse(extras, Notification.EXTRA_VERIFICATION_ICON, seenBitmaps)
-
-        // Messages
-        val messages =
-            Notification.MessagingStyle.Message.getMessagesFromBundleArray(
-                extras.getParcelableArray(Notification.EXTRA_MESSAGES)
-            )
-        val messagesUse =
-            messages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
-        val historicMessages =
-            Notification.MessagingStyle.Message.getMessagesFromBundleArray(
-                extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES)
-            )
-        val historyicMessagesUse =
-            historicMessages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
-
-        // Extenders
-        val carExtender = extras.getBundle(CAR_EXTENSIONS)
-        val carExtenderSize = carExtender?.let { computeBundleSize(it) } ?: 0
-        val carExtenderIcon =
-            computeParcelableUse(carExtender, CAR_EXTENSIONS_LARGE_ICON, seenBitmaps)
-
-        val tvExtender = extras.getBundle(TV_EXTENSIONS)
-        val tvExtenderSize = tvExtender?.let { computeBundleSize(it) } ?: 0
-
-        val wearExtender = extras.getBundle(WEARABLE_EXTENSIONS)
-        val wearExtenderSize = wearExtender?.let { computeBundleSize(it) } ?: 0
-        val wearExtenderBackground =
-            computeParcelableUse(wearExtender, WEARABLE_EXTENSIONS_BACKGROUND, seenBitmaps)
-
-        val style = notification.notificationStyle
-        val hasCustomView = notification.contentView != null || notification.bigContentView != null
-        val extrasSize = computeBundleSize(extras)
-
-        return NotificationObjectUsage(
-            smallIconUse,
-            largeIconUse,
-            extrasSize,
-            style?.simpleName,
-            bigPictureIconUse +
-                peopleUse +
-                callingPersonUse +
-                verificationIconUse +
-                messagesUse +
-                historyicMessagesUse,
-            bigPictureUse,
-            carExtenderSize +
-                carExtenderIcon +
-                tvExtenderSize +
-                wearExtenderSize +
-                wearExtenderBackground,
-            hasCustomView
+    /** Renders a table of notification object usage into passed [PrintWriter]. */
+    private fun dumpNotificationObjects(pw: PrintWriter, memoryUse: List<NotificationMemoryUsage>) {
+        pw.println("Notification Object Usage")
+        pw.println("-----------")
+        pw.println(
+            "Package".padEnd(35) +
+                "\t\tSmall\tLarge\t${"Style".padEnd(15)}\t\tStyle\tBig\tExtend.\tExtras\tCustom"
         )
+        pw.println("".padEnd(35) + "\t\tIcon\tIcon\t${"".padEnd(15)}\t\tIcon\tPicture\t \t \tView")
+        pw.println()
+
+        memoryUse.forEach { use ->
+            pw.println(
+                use.packageName.padEnd(35) +
+                    "\t\t" +
+                    "${use.objectUsage.smallIcon}\t${use.objectUsage.largeIcon}\t" +
+                    (use.objectUsage.style?.take(15) ?: "").padEnd(15) +
+                    "\t\t${use.objectUsage.styleIcon}\t" +
+                    "${use.objectUsage.bigPicture}\t${use.objectUsage.extender}\t" +
+                    "${use.objectUsage.extras}\t${use.objectUsage.hasCustomView}\t" +
+                    use.notificationKey
+            )
+        }
+
+        // Calculate totals for easily glanceable summary.
+        data class Totals(
+            var smallIcon: Int = 0,
+            var largeIcon: Int = 0,
+            var styleIcon: Int = 0,
+            var bigPicture: Int = 0,
+            var extender: Int = 0,
+            var extras: Int = 0,
+        )
+
+        val totals =
+            memoryUse.fold(Totals()) { t, usage ->
+                t.smallIcon += usage.objectUsage.smallIcon
+                t.largeIcon += usage.objectUsage.largeIcon
+                t.styleIcon += usage.objectUsage.styleIcon
+                t.bigPicture += usage.objectUsage.bigPicture
+                t.extender += usage.objectUsage.extender
+                t.extras += usage.objectUsage.extras
+                t
+            }
+
+        pw.println()
+        pw.println("TOTALS")
+        pw.println(
+            "".padEnd(35) +
+                "\t\t" +
+                "${toKb(totals.smallIcon)}\t${toKb(totals.largeIcon)}\t" +
+                "".padEnd(15) +
+                "\t\t${toKb(totals.styleIcon)}\t" +
+                "${toKb(totals.bigPicture)}\t${toKb(totals.extender)}\t" +
+                toKb(totals.extras)
+        )
+        pw.println()
     }
 
-    /**
-     * Calculates size of the bundle data (excluding FDs and other shared objects like ashmem
-     * bitmaps). Can be slow.
-     */
-    private fun computeBundleSize(extras: Bundle): Int {
-        val parcel = Parcel.obtain()
-        try {
-            extras.writeToParcel(parcel, 0)
-            return parcel.dataSize()
-        } finally {
-            parcel.recycle()
-        }
+    /** Renders a table of notification view usage into passed [PrintWriter] */
+    private fun dumpNotificationViewUsage(
+        pw: PrintWriter,
+        memoryUse: List<NotificationMemoryUsage>,
+    ) {
+
+        data class Totals(
+            var smallIcon: Int = 0,
+            var largeIcon: Int = 0,
+            var style: Int = 0,
+            var customViews: Int = 0,
+            var softwareBitmapsPenalty: Int = 0,
+        )
+
+        val totals = Totals()
+        pw.println("Notification View Usage")
+        pw.println("-----------")
+        pw.println("View Type".padEnd(24) + "\tSmall\tLarge\tStyle\tCustom\tSoftware")
+        pw.println("".padEnd(24) + "\tIcon\tIcon\tUse\tView\tBitmaps")
+        pw.println()
+        memoryUse
+            .filter { it.viewUsage.isNotEmpty() }
+            .forEach { use ->
+                pw.println(use.packageName + " " + use.notificationKey)
+                use.viewUsage.forEach { view ->
+                    pw.println(
+                        "  ${view.viewType.toString().padEnd(24)}\t${view.smallIcon}" +
+                            "\t${view.largeIcon}\t${view.style}" +
+                            "\t${view.customViews}\t${view.softwareBitmapsPenalty}"
+                    )
+
+                    if (view.viewType == ViewType.TOTAL) {
+                        totals.smallIcon += view.smallIcon
+                        totals.largeIcon += view.largeIcon
+                        totals.style += view.style
+                        totals.customViews += view.customViews
+                        totals.softwareBitmapsPenalty += view.softwareBitmapsPenalty
+                    }
+                }
+            }
+        pw.println()
+        pw.println("TOTALS")
+        pw.println(
+            "  ${"".padEnd(24)}\t${toKb(totals.smallIcon)}" +
+                "\t${toKb(totals.largeIcon)}\t${toKb(totals.style)}" +
+                "\t${toKb(totals.customViews)}\t${toKb(totals.softwareBitmapsPenalty)}"
+        )
+        pw.println()
     }
 
-    /**
-     * Deserializes [Icon], [Bitmap] or [Person] from extras and computes its memory use. Returns 0
-     * if the key does not exist in extras.
-     */
-    private fun computeParcelableUse(extras: Bundle?, key: String, seenBitmaps: HashSet<Int>): Int {
-        return when (val parcelable = extras?.getParcelable<Parcelable>(key)) {
-            is Bitmap -> computeBitmapUse(parcelable, seenBitmaps)
-            is Icon -> computeIconUse(parcelable, seenBitmaps)
-            is Person -> computeIconUse(parcelable.icon, seenBitmaps)
-            else -> 0
-        }
-    }
-
-    /**
-     * Calculates the byte size of bitmaps or data in the Icon object. Returns 0 if the icon is
-     * defined via Uri or a resource.
-     *
-     * @return memory usage in bytes or 0 if the icon is Uri/Resource based
-     */
-    private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>) =
-        when (icon?.type) {
-            Icon.TYPE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
-            Icon.TYPE_ADAPTIVE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
-            Icon.TYPE_DATA -> computeDataUse(icon, seenBitmaps)
-            else -> 0
-        }
-
-    /**
-     * Returns the amount of memory a given bitmap is using. If the bitmap reference is part of
-     * seenBitmaps set, this method returns 0 to avoid double counting.
-     *
-     * @return memory usage of the bitmap in bytes
-     */
-    private fun computeBitmapUse(bitmap: Bitmap, seenBitmaps: HashSet<Int>? = null): Int {
-        val refId = System.identityHashCode(bitmap)
-        if (seenBitmaps?.contains(refId) == true) {
-            return 0
-        }
-
-        seenBitmaps?.add(refId)
-        return bitmap.allocationByteCount
-    }
-
-    private fun computeDataUse(icon: Icon, seenBitmaps: HashSet<Int>): Int {
-        val refId = System.identityHashCode(icon.dataBytes)
-        if (seenBitmaps.contains(refId)) {
-            return 0
-        }
-
-        seenBitmaps.add(refId)
-        return icon.dataLength
+    private fun toKb(bytes: Int): String {
+        return (bytes / 1024).toString() + " KB"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
new file mode 100644
index 0000000..a0bee15
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
@@ -0,0 +1,173 @@
+package com.android.systemui.statusbar.notification.logging
+
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import com.android.internal.R
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.util.children
+
+/** Walks view hiearchy of a given notification to estimate its memory use. */
+internal object NotificationMemoryViewWalker {
+
+    private const val TAG = "NotificationMemory"
+
+    /** Builder for [NotificationViewUsage] objects. */
+    private class UsageBuilder {
+        private var smallIcon: Int = 0
+        private var largeIcon: Int = 0
+        private var systemIcons: Int = 0
+        private var style: Int = 0
+        private var customViews: Int = 0
+        private var softwareBitmaps = 0
+
+        fun addSmallIcon(smallIconUse: Int) = apply { smallIcon += smallIconUse }
+        fun addLargeIcon(largeIconUse: Int) = apply { largeIcon += largeIconUse }
+        fun addSystem(systemIconUse: Int) = apply { systemIcons += systemIconUse }
+        fun addStyle(styleUse: Int) = apply { style += styleUse }
+        fun addSoftwareBitmapPenalty(softwareBitmapUse: Int) = apply {
+            softwareBitmaps += softwareBitmapUse
+        }
+
+        fun addCustomViews(customViewsUse: Int) = apply { customViews += customViewsUse }
+
+        fun build(viewType: ViewType): NotificationViewUsage {
+            return NotificationViewUsage(
+                viewType = viewType,
+                smallIcon = smallIcon,
+                largeIcon = largeIcon,
+                systemIcons = systemIcons,
+                style = style,
+                customViews = customViews,
+                softwareBitmapsPenalty = softwareBitmaps,
+            )
+        }
+    }
+
+    /**
+     * Returns memory usage of public and private views contained in passed
+     * [ExpandableNotificationRow]
+     */
+    fun getViewUsage(row: ExpandableNotificationRow?): List<NotificationViewUsage> {
+        if (row == null) {
+            return listOf()
+        }
+
+        // The ordering here is significant since it determines deduplication of seen drawables.
+        return listOf(
+            getViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, row.privateLayout?.expandedChild),
+            getViewUsage(ViewType.PRIVATE_CONTRACTED_VIEW, row.privateLayout?.contractedChild),
+            getViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, row.privateLayout?.headsUpChild),
+            getViewUsage(ViewType.PUBLIC_VIEW, row.publicLayout),
+            getTotalUsage(row)
+        )
+    }
+
+    /**
+     * Calculate total usage of all views - we need to do a separate traversal to make sure we don't
+     * double count fields.
+     */
+    private fun getTotalUsage(row: ExpandableNotificationRow): NotificationViewUsage {
+        val totalUsage = UsageBuilder()
+        val seenObjects = hashSetOf<Int>()
+
+        row.publicLayout?.let { computeViewHierarchyUse(it, totalUsage, seenObjects) }
+        row.privateLayout?.let { child ->
+            for (view in listOf(child.expandedChild, child.contractedChild, child.headsUpChild)) {
+                (view as? ViewGroup)?.let { v ->
+                    computeViewHierarchyUse(v, totalUsage, seenObjects)
+                }
+            }
+        }
+        return totalUsage.build(ViewType.TOTAL)
+    }
+
+    private fun getViewUsage(
+        type: ViewType,
+        rootView: View?,
+        seenObjects: HashSet<Int> = hashSetOf()
+    ): NotificationViewUsage {
+        val usageBuilder = UsageBuilder()
+        (rootView as? ViewGroup)?.let { computeViewHierarchyUse(it, usageBuilder, seenObjects) }
+        return usageBuilder.build(type)
+    }
+
+    private fun computeViewHierarchyUse(
+        rootView: ViewGroup,
+        builder: UsageBuilder,
+        seenObjects: HashSet<Int> = hashSetOf(),
+    ) {
+        for (child in rootView.children) {
+            if (child is ViewGroup) {
+                computeViewHierarchyUse(child, builder, seenObjects)
+            } else {
+                computeViewUse(child, builder, seenObjects)
+            }
+        }
+    }
+
+    private fun computeViewUse(view: View, builder: UsageBuilder, seenObjects: HashSet<Int>) {
+        if (view !is ImageView) return
+        val drawable = view.drawable ?: return
+        val drawableRef = System.identityHashCode(drawable)
+        if (seenObjects.contains(drawableRef)) return
+        val drawableUse = computeDrawableUse(drawable, seenObjects)
+        // TODO(b/235451049): We need to make sure we traverse large icon before small icon -
+        // sometimes the large icons are assigned to small icon views and we want to
+        // attribute them to large view in those cases.
+        when (view.id) {
+            R.id.left_icon,
+            R.id.icon,
+            R.id.conversation_icon -> builder.addSmallIcon(drawableUse)
+            R.id.right_icon -> builder.addLargeIcon(drawableUse)
+            R.id.big_picture -> builder.addStyle(drawableUse)
+            // Elements that are part of platform with resources
+            R.id.phishing_alert,
+            R.id.feedback,
+            R.id.alerted_icon,
+            R.id.expand_button_icon,
+            R.id.remote_input_send -> builder.addSystem(drawableUse)
+            // Custom view ImageViews
+            else -> {
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Custom view: ${identifierForView(view)}")
+                }
+                builder.addCustomViews(drawableUse)
+            }
+        }
+
+        if (isDrawableSoftwareBitmap(drawable)) {
+            builder.addSoftwareBitmapPenalty(drawableUse)
+        }
+
+        seenObjects.add(drawableRef)
+    }
+
+    private fun computeDrawableUse(drawable: Drawable, seenObjects: HashSet<Int>): Int =
+        when (drawable) {
+            is BitmapDrawable -> {
+                val ref = System.identityHashCode(drawable.bitmap)
+                if (seenObjects.contains(ref)) {
+                    0
+                } else {
+                    seenObjects.add(ref)
+                    drawable.bitmap.allocationByteCount
+                }
+            }
+            else -> 0
+        }
+
+    private fun isDrawableSoftwareBitmap(drawable: Drawable) =
+        drawable is BitmapDrawable && drawable.bitmap.config != Bitmap.Config.HARDWARE
+
+    private fun identifierForView(view: View) =
+        if (view.id == View.NO_ID) {
+            "no-id"
+        } else {
+            view.resources.getResourceName(view.id)
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
index fe03b2a..10197a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.notification.logging
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
 import com.android.systemui.log.dagger.NotificationRenderLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.INFO
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.statusbar.notification.stack.NotificationSection
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 755e3e1..d29298a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -613,22 +613,21 @@
     protected void resetAllContentAlphas() {}
 
     @Override
-    protected void applyRoundness() {
+    public void applyRoundness() {
         super.applyRoundness();
-        applyBackgroundRoundness(getCurrentBackgroundRadiusTop(),
-                getCurrentBackgroundRadiusBottom());
+        applyBackgroundRoundness(getTopCornerRadius(), getBottomCornerRadius());
     }
 
     @Override
-    public float getCurrentBackgroundRadiusTop() {
+    public float getTopCornerRadius() {
         float fraction = getInterpolatedAppearAnimationFraction();
-        return MathUtils.lerp(0, super.getCurrentBackgroundRadiusTop(), fraction);
+        return MathUtils.lerp(0, super.getTopCornerRadius(), fraction);
     }
 
     @Override
-    public float getCurrentBackgroundRadiusBottom() {
+    public float getBottomCornerRadius() {
         float fraction = getInterpolatedAppearAnimationFraction();
-        return MathUtils.lerp(0, super.getCurrentBackgroundRadiusBottom(), fraction);
+        return MathUtils.lerp(0, super.getBottomCornerRadius(), fraction);
     }
 
     private void applyBackgroundRoundness(float topRadius, float bottomRadius) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 1b00648..9e7717c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -93,6 +93,7 @@
 import com.android.systemui.statusbar.notification.NotificationFadeAware;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController;
 import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -154,7 +155,9 @@
         void onLayout();
     }
 
-    /** Listens for changes to the expansion state of this row. */
+    /**
+     * Listens for changes to the expansion state of this row.
+     */
     public interface OnExpansionChangedListener {
         void onExpansionChanged(boolean isExpanded);
     }
@@ -183,22 +186,34 @@
     private int mNotificationLaunchHeight;
     private boolean mMustStayOnScreen;
 
-    /** Does this row contain layouts that can adapt to row expansion */
+    /**
+     * Does this row contain layouts that can adapt to row expansion
+     */
     private boolean mExpandable;
-    /** Has the user actively changed the expansion state of this row */
+    /**
+     * Has the user actively changed the expansion state of this row
+     */
     private boolean mHasUserChangedExpansion;
-    /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */
+    /**
+     * If {@link #mHasUserChangedExpansion}, has the user expanded this row
+     */
     private boolean mUserExpanded;
-    /** Whether the blocking helper is showing on this notification (even if dismissed) */
+    /**
+     * Whether the blocking helper is showing on this notification (even if dismissed)
+     */
     private boolean mIsBlockingHelperShowing;
 
     /**
      * Has this notification been expanded while it was pinned
      */
     private boolean mExpandedWhenPinned;
-    /** Is the user touching this row */
+    /**
+     * Is the user touching this row
+     */
     private boolean mUserLocked;
-    /** Are we showing the "public" version */
+    /**
+     * Are we showing the "public" version
+     */
     private boolean mShowingPublic;
     private boolean mSensitive;
     private boolean mSensitiveHiddenInGeneral;
@@ -351,11 +366,14 @@
     private boolean mWasChildInGroupWhenRemoved;
     private NotificationInlineImageResolver mImageResolver;
     private NotificationMediaManager mMediaManager;
-    @Nullable private OnExpansionChangedListener mExpansionChangedListener;
-    @Nullable private Runnable mOnIntrinsicHeightReachedRunnable;
+    @Nullable
+    private OnExpansionChangedListener mExpansionChangedListener;
+    @Nullable
+    private Runnable mOnIntrinsicHeightReachedRunnable;
 
     private float mTopRoundnessDuringLaunchAnimation;
     private float mBottomRoundnessDuringLaunchAnimation;
+    private boolean mIsNotificationGroupCornerEnabled;
 
     /**
      * Returns whether the given {@code statusBarNotification} is a system notification.
@@ -574,14 +592,18 @@
         }
     }
 
-    /** Called when the notification's ranking was changed (but nothing else changed). */
+    /**
+     * Called when the notification's ranking was changed (but nothing else changed).
+     */
     public void onNotificationRankingUpdated() {
         if (mMenuRow != null) {
             mMenuRow.onNotificationUpdated(mEntry.getSbn());
         }
     }
 
-    /** Call when bubble state has changed and the button on the notification should be updated. */
+    /**
+     * Call when bubble state has changed and the button on the notification should be updated.
+     */
     public void updateBubbleButton() {
         for (NotificationContentView l : mLayouts) {
             l.updateBubbleButton(mEntry);
@@ -620,6 +642,7 @@
 
     /**
      * Sets a supplier that can determine whether the keyguard is secure or not.
+     *
      * @param secureStateProvider A function that returns true if keyguard is secure.
      */
     public void setSecureStateProvider(BooleanSupplier secureStateProvider) {
@@ -781,7 +804,9 @@
         mChildrenContainer.setUntruncatedChildCount(childCount);
     }
 
-    /** Called after children have been attached to set the expansion states */
+    /**
+     * Called after children have been attached to set the expansion states
+     */
     public void resetChildSystemExpandedStates() {
         if (isSummaryWithChildren()) {
             mChildrenContainer.updateExpansionStates();
@@ -791,7 +816,7 @@
     /**
      * Add a child notification to this view.
      *
-     * @param row the row to add
+     * @param row        the row to add
      * @param childIndex the index to add it at, if -1 it will be added at the end
      */
     public void addChildNotification(ExpandableNotificationRow row, int childIndex) {
@@ -809,10 +834,12 @@
         }
         onAttachedChildrenCountChanged();
         row.setIsChildInGroup(false, null);
-        row.setBottomRoundness(0.0f, false /* animate */);
+        row.requestBottomRoundness(0.0f, /* animate = */ false, SourceType.DefaultValue);
     }
 
-    /** Returns the child notification at [index], or null if no such child. */
+    /**
+     * Returns the child notification at [index], or null if no such child.
+     */
     @Nullable
     public ExpandableNotificationRow getChildNotificationAt(int index) {
         if (mChildrenContainer == null
@@ -834,7 +861,7 @@
 
     /**
      * @param isChildInGroup Is this notification now in a group
-     * @param parent the new parent notification
+     * @param parent         the new parent notification
      */
     public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) {
         if (mExpandAnimationRunning && !isChildInGroup && mNotificationParent != null) {
@@ -898,7 +925,9 @@
         return mChildrenContainer == null ? null : mChildrenContainer.getAttachedChildren();
     }
 
-    /** Updates states of all children. */
+    /**
+     * Updates states of all children.
+     */
     public void updateChildrenStates(AmbientState ambientState) {
         if (mIsSummaryWithChildren) {
             ExpandableViewState parentState = getViewState();
@@ -906,21 +935,27 @@
         }
     }
 
-    /** Applies children states. */
+    /**
+     * Applies children states.
+     */
     public void applyChildrenState() {
         if (mIsSummaryWithChildren) {
             mChildrenContainer.applyState();
         }
     }
 
-    /** Prepares expansion changed. */
+    /**
+     * Prepares expansion changed.
+     */
     public void prepareExpansionChanged() {
         if (mIsSummaryWithChildren) {
             mChildrenContainer.prepareExpansionChanged();
         }
     }
 
-    /** Starts child animations. */
+    /**
+     * Starts child animations.
+     */
     public void startChildAnimation(AnimationProperties properties) {
         if (mIsSummaryWithChildren) {
             mChildrenContainer.startAnimationToState(properties);
@@ -984,7 +1019,7 @@
         if (mIsSummaryWithChildren) {
             return mChildrenContainer.getIntrinsicHeight();
         }
-        if(mExpandedWhenPinned) {
+        if (mExpandedWhenPinned) {
             return Math.max(getMaxExpandHeight(), getHeadsUpHeight());
         } else if (atLeastMinHeight) {
             return Math.max(getCollapsedHeight(), getHeadsUpHeight());
@@ -1079,18 +1114,22 @@
         updateClickAndFocus();
     }
 
-    /** The click listener for the bubble button. */
+    /**
+     * The click listener for the bubble button.
+     */
     public View.OnClickListener getBubbleClickListener() {
         return v -> {
             if (mBubblesManagerOptional.isPresent()) {
                 mBubblesManagerOptional.get()
-                    .onUserChangedBubble(mEntry, !mEntry.isBubble() /* createBubble */);
+                        .onUserChangedBubble(mEntry, !mEntry.isBubble() /* createBubble */);
             }
             mHeadsUpManager.removeNotification(mEntry.getKey(), true /* releaseImmediately */);
         };
     }
 
-    /** The click listener for the snooze button. */
+    /**
+     * The click listener for the snooze button.
+     */
     public View.OnClickListener getSnoozeClickListener(MenuItem item) {
         return v -> {
             // Dismiss a snoozed notification if one is still left behind
@@ -1252,7 +1291,7 @@
     }
 
     public void setContentBackground(int customBackgroundColor, boolean animate,
-            NotificationContentView notificationContentView) {
+                                     NotificationContentView notificationContentView) {
         if (getShowingLayout() == notificationContentView) {
             setTintColor(customBackgroundColor, animate);
         }
@@ -1487,7 +1526,7 @@
             l.setAlpha(alpha);
         }
         if (mChildrenContainer != null) {
-            mChildrenContainer.setContentAlpha(alpha);
+            mChildrenContainer.setAlpha(alpha);
         }
     }
 
@@ -1637,7 +1676,9 @@
         setTargetPoint(null);
     }
 
-    /** Shows the given feedback icon, or hides the icon if null. */
+    /**
+     * Shows the given feedback icon, or hides the icon if null.
+     */
     public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
         if (mIsSummaryWithChildren) {
             mChildrenContainer.setFeedbackIcon(icon);
@@ -1646,7 +1687,9 @@
         mPublicLayout.setFeedbackIcon(icon);
     }
 
-    /** Sets the last time the notification being displayed audibly alerted the user. */
+    /**
+     * Sets the last time the notification being displayed audibly alerted the user.
+     */
     public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) {
         long timeSinceAlertedAudibly = System.currentTimeMillis() - lastAudiblyAlertedMs;
         boolean alertedRecently = timeSinceAlertedAudibly < RECENTLY_ALERTED_THRESHOLD_MS;
@@ -1700,7 +1743,9 @@
         Trace.endSection();
     }
 
-    /** Generates and appends "(MessagingStyle)" type tag to passed string for tracing. */
+    /**
+     * Generates and appends "(MessagingStyle)" type tag to passed string for tracing.
+     */
     @NonNull
     private String appendTraceStyleTag(@NonNull String traceTag) {
         if (!Trace.isEnabled()) {
@@ -1721,7 +1766,7 @@
         super.onFinishInflate();
         mPublicLayout = findViewById(R.id.expandedPublic);
         mPrivateLayout = findViewById(R.id.expanded);
-        mLayouts = new NotificationContentView[] {mPrivateLayout, mPublicLayout};
+        mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout};
 
         for (NotificationContentView l : mLayouts) {
             l.setExpandClickListener(mExpandClickListener);
@@ -1740,6 +1785,7 @@
             mChildrenContainer.setIsLowPriority(mIsLowPriority);
             mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this);
             mChildrenContainer.onNotificationUpdated();
+            mChildrenContainer.enableNotificationGroupCorner(mIsNotificationGroupCornerEnabled);
 
             mTranslateableViews.add(mChildrenContainer);
         });
@@ -1796,6 +1842,7 @@
     /**
      * Perform a smart action which triggers a longpress (expose guts).
      * Based on the semanticAction passed, may update the state of the guts view.
+     *
      * @param semanticAction associated with this smart action click
      */
     public void doSmartActionClick(int x, int y, int semanticAction) {
@@ -1939,9 +1986,10 @@
 
     /**
      * Set the dismiss behavior of the view.
+     *
      * @param usingRowTranslationX {@code true} if the view should translate using regular
-     *                                          translationX, otherwise the contents will be
-     *                                          translated.
+     *                             translationX, otherwise the contents will be
+     *                             translated.
      */
     @Override
     public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) {
@@ -1955,6 +2003,14 @@
             if (previousTranslation != 0) {
                 setTranslation(previousTranslation);
             }
+            if (mChildrenContainer != null) {
+                List<ExpandableNotificationRow> notificationChildren =
+                        mChildrenContainer.getAttachedChildren();
+                for (int i = 0; i < notificationChildren.size(); i++) {
+                    ExpandableNotificationRow child = notificationChildren.get(i);
+                    child.setDismissUsingRowTranslationX(usingRowTranslationX);
+                }
+            }
         }
     }
 
@@ -2009,7 +2065,7 @@
     }
 
     public Animator getTranslateViewAnimator(final float leftTarget,
-            AnimatorUpdateListener listener) {
+                                             AnimatorUpdateListener listener) {
         if (mTranslateAnim != null) {
             mTranslateAnim.cancel();
         }
@@ -2115,7 +2171,7 @@
                             NotificationLaunchAnimatorController.ANIMATION_DURATION_TOP_ROUNDING));
             float startTop = params.getStartNotificationTop();
             top = (int) Math.min(MathUtils.lerp(startTop,
-                    params.getTop(), expandProgress),
+                            params.getTop(), expandProgress),
                     startTop);
         } else {
             top = params.getTop();
@@ -2151,29 +2207,30 @@
         }
         setTranslationY(top);
 
-        mTopRoundnessDuringLaunchAnimation = params.getTopCornerRadius() / mOutlineRadius;
-        mBottomRoundnessDuringLaunchAnimation = params.getBottomCornerRadius() / mOutlineRadius;
+        final float maxRadius = getMaxRadius();
+        mTopRoundnessDuringLaunchAnimation = params.getTopCornerRadius() / maxRadius;
+        mBottomRoundnessDuringLaunchAnimation = params.getBottomCornerRadius() / maxRadius;
         invalidateOutline();
 
         mBackgroundNormal.setExpandAnimationSize(params.getWidth(), actualHeight);
     }
 
     @Override
-    public float getCurrentTopRoundness() {
+    public float getTopRoundness() {
         if (mExpandAnimationRunning) {
             return mTopRoundnessDuringLaunchAnimation;
         }
 
-        return super.getCurrentTopRoundness();
+        return super.getTopRoundness();
     }
 
     @Override
-    public float getCurrentBottomRoundness() {
+    public float getBottomRoundness() {
         if (mExpandAnimationRunning) {
             return mBottomRoundnessDuringLaunchAnimation;
         }
 
-        return super.getCurrentBottomRoundness();
+        return super.getBottomRoundness();
     }
 
     public void setExpandAnimationRunning(boolean expandAnimationRunning) {
@@ -2284,7 +2341,7 @@
     /**
      * Set this notification to be expanded by the user
      *
-     * @param userExpanded whether the user wants this notification to be expanded
+     * @param userExpanded        whether the user wants this notification to be expanded
      * @param allowChildExpansion whether a call to this method allows expanding children
      */
     public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
@@ -2434,7 +2491,7 @@
 
     /**
      * @return {@code true} if the notification can show it's heads up layout. This is mostly true
-     *         except for legacy use cases.
+     * except for legacy use cases.
      */
     public boolean canShowHeadsUp() {
         if (mOnKeyguard && !isDozing() && !isBypassEnabled()) {
@@ -2625,7 +2682,7 @@
 
     @Override
     public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
-            long duration) {
+                                 long duration) {
         if (getVisibility() == GONE) {
             // If we are GONE, the hideSensitive parameter will not be calculated and always be
             // false, which is incorrect, let's wait until a real call comes in later.
@@ -2658,9 +2715,9 @@
 
     private void animateShowingPublic(long delay, long duration, boolean showingPublic) {
         View[] privateViews = mIsSummaryWithChildren
-                ? new View[] {mChildrenContainer}
-                : new View[] {mPrivateLayout};
-        View[] publicViews = new View[] {mPublicLayout};
+                ? new View[]{mChildrenContainer}
+                : new View[]{mPrivateLayout};
+        View[] publicViews = new View[]{mPublicLayout};
         View[] hiddenChildren = showingPublic ? privateViews : publicViews;
         View[] shownChildren = showingPublic ? publicViews : privateViews;
         for (final View hiddenView : hiddenChildren) {
@@ -2693,8 +2750,8 @@
 
     /**
      * @return Whether this view is allowed to be dismissed. Only valid for visible notifications as
-     *         otherwise some state might not be updated. To request about the general clearability
-     *         see {@link NotificationEntry#isDismissable()}.
+     * otherwise some state might not be updated. To request about the general clearability
+     * see {@link NotificationEntry#isDismissable()}.
      */
     public boolean canViewBeDismissed() {
         return mEntry.isDismissable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral);
@@ -2777,8 +2834,13 @@
     }
 
     @Override
-    public long performRemoveAnimation(long duration, long delay, float translationDirection,
-            boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable,
+    public long performRemoveAnimation(
+            long duration,
+            long delay,
+            float translationDirection,
+            boolean isHeadsUpAnimation,
+            float endLocation,
+            Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener) {
         if (mMenuRow != null && mMenuRow.isMenuVisible()) {
             Animator anim = getTranslateViewAnimator(0f, null /* listener */);
@@ -2828,7 +2890,9 @@
         }
     }
 
-    /** Gets the last value set with {@link #setNotificationFaded(boolean)} */
+    /**
+     * Gets the last value set with {@link #setNotificationFaded(boolean)}
+     */
     @Override
     public boolean isNotificationFaded() {
         return mIsFaded;
@@ -2843,7 +2907,7 @@
      * notifications return false from {@link #hasOverlappingRendering()} and delegate the
      * layerType to child views which really need it in order to render correctly, such as icon
      * views or the conversation face pile.
-     *
+     * <p>
      * Another compounding factor for notifications is that we change clipping on each frame of the
      * animation, so the hardware layer isn't able to do any caching at the top level, but the
      * individual elements we render with hardware layers (e.g. icons) cache wonderfully because we
@@ -2869,7 +2933,9 @@
         }
     }
 
-    /** Private helper for iterating over the layouts and children containers to set faded state */
+    /**
+     * Private helper for iterating over the layouts and children containers to set faded state
+     */
     private void setNotificationFadedOnChildren(boolean faded) {
         delegateNotificationFaded(mChildrenContainer, faded);
         for (NotificationContentView layout : mLayouts) {
@@ -2897,7 +2963,7 @@
      * Because RemoteInputView is designed to be an opaque view that overlaps the Actions row, the
      * row should require overlapping rendering to ensure that the overlapped view doesn't bleed
      * through when alpha fading.
-     *
+     * <p>
      * Note that this currently works for top-level notifications which squish their height down
      * while collapsing the shade, but does not work for children inside groups, because the
      * accordion affect does not apply to those views, so super.hasOverlappingRendering() will
@@ -2976,7 +3042,7 @@
             return mGuts.getIntrinsicHeight();
         } else if (!ignoreTemporaryStates && canShowHeadsUp() && mIsHeadsUp
                 && mHeadsUpManager.isTrackingHeadsUp()) {
-                return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);
+            return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);
         } else if (mIsSummaryWithChildren && !isGroupExpanded() && !shouldShowPublic()) {
             return mChildrenContainer.getMinHeight();
         } else if (!ignoreTemporaryStates && canShowHeadsUp() && mIsHeadsUp) {
@@ -3218,8 +3284,8 @@
             MenuItem snoozeMenu = provider.getSnoozeMenuItem(getContext());
             if (snoozeMenu != null) {
                 AccessibilityAction action = new AccessibilityAction(R.id.action_snooze,
-                    getContext().getResources()
-                        .getString(R.string.notification_menu_snooze_action));
+                        getContext().getResources()
+                                .getString(R.string.notification_menu_snooze_action));
                 info.addAction(action);
             }
         }
@@ -3280,17 +3346,17 @@
             NotificationContentView contentView = (NotificationContentView) child;
             if (isClippingNeeded()) {
                 return true;
-            } else if (!hasNoRounding()
-                    && contentView.shouldClipToRounding(getCurrentTopRoundness() != 0.0f,
-                    getCurrentBottomRoundness() != 0.0f)) {
+            } else if (hasRoundedCorner()
+                    && contentView.shouldClipToRounding(getTopRoundness() != 0.0f,
+                    getBottomRoundness() != 0.0f)) {
                 return true;
             }
         } else if (child == mChildrenContainer) {
-            if (isClippingNeeded() || !hasNoRounding()) {
+            if (isClippingNeeded() || hasRoundedCorner()) {
                 return true;
             }
         } else if (child instanceof NotificationGuts) {
-            return !hasNoRounding();
+            return hasRoundedCorner();
         }
         return super.childNeedsClipping(child);
     }
@@ -3316,14 +3382,17 @@
     }
 
     @Override
-    protected void applyRoundness() {
+    public void applyRoundness() {
         super.applyRoundness();
         applyChildrenRoundness();
     }
 
     private void applyChildrenRoundness() {
         if (mIsSummaryWithChildren) {
-            mChildrenContainer.setCurrentBottomRoundness(getCurrentBottomRoundness());
+            mChildrenContainer.requestBottomRoundness(
+                    getBottomRoundness(),
+                    /* animate = */ false,
+                    SourceType.DefaultValue);
         }
     }
 
@@ -3335,10 +3404,6 @@
         return super.getCustomClipPath(child);
     }
 
-    private boolean hasNoRounding() {
-        return getCurrentBottomRoundness() == 0.0f && getCurrentTopRoundness() == 0.0f;
-    }
-
     public boolean isMediaRow() {
         return mEntry.getSbn().getNotification().isMediaNotification();
     }
@@ -3434,6 +3499,7 @@
     public interface LongPressListener {
         /**
          * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
+         *
          * @return whether the longpress was handled
          */
         boolean onLongPress(View v, int x, int y, MenuItem item);
@@ -3455,6 +3521,7 @@
     public interface CoordinateOnClickListener {
         /**
          * Equivalent to {@link View.OnClickListener#onClick(View)} with coordinates
+         *
          * @return whether the click was handled
          */
         boolean onClick(View v, int x, int y, MenuItem item);
@@ -3511,7 +3578,19 @@
     private void setTargetPoint(Point p) {
         mTargetPoint = p;
     }
+
     public Point getTargetPoint() {
         return mTargetPoint;
     }
+
+    /**
+     * Enable the support for rounded corner in notification group
+     * @param enabled true if is supported
+     */
+    public void enableNotificationGroupCorner(boolean enabled) {
+        mIsNotificationGroupCornerEnabled = enabled;
+        if (mChildrenContainer != null) {
+            mChildrenContainer.enableNotificationGroupCorner(mIsNotificationGroupCornerEnabled);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index a493a67..842526e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -231,6 +231,8 @@
                 mStatusBarStateController.removeCallback(mStatusBarStateListener);
             }
         });
+        mView.enableNotificationGroupCorner(
+                mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_CORNER));
     }
 
     private final StatusBarStateController.StateListener mStatusBarStateListener =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index d58fe3b..4fde5d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -28,46 +28,21 @@
 import android.view.ViewOutlineProvider;
 
 import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.AnimatableProperty;
-import com.android.systemui.statusbar.notification.PropertyAnimator;
-import com.android.systemui.statusbar.notification.stack.AnimationProperties;
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.notification.RoundableState;
+import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
 
 /**
  * Like {@link ExpandableView}, but setting an outline for the height and clipping.
  */
 public abstract class ExpandableOutlineView extends ExpandableView {
 
-    private static final AnimatableProperty TOP_ROUNDNESS = AnimatableProperty.from(
-            "topRoundness",
-            ExpandableOutlineView::setTopRoundnessInternal,
-            ExpandableOutlineView::getCurrentTopRoundness,
-            R.id.top_roundess_animator_tag,
-            R.id.top_roundess_animator_end_tag,
-            R.id.top_roundess_animator_start_tag);
-    private static final AnimatableProperty BOTTOM_ROUNDNESS = AnimatableProperty.from(
-            "bottomRoundness",
-            ExpandableOutlineView::setBottomRoundnessInternal,
-            ExpandableOutlineView::getCurrentBottomRoundness,
-            R.id.bottom_roundess_animator_tag,
-            R.id.bottom_roundess_animator_end_tag,
-            R.id.bottom_roundess_animator_start_tag);
-    private static final AnimationProperties ROUNDNESS_PROPERTIES =
-            new AnimationProperties().setDuration(
-                    StackStateAnimator.ANIMATION_DURATION_CORNER_RADIUS);
+    private RoundableState mRoundableState;
     private static final Path EMPTY_PATH = new Path();
-
     private final Rect mOutlineRect = new Rect();
-    private final Path mClipPath = new Path();
     private boolean mCustomOutline;
     private float mOutlineAlpha = -1f;
-    protected float mOutlineRadius;
     private boolean mAlwaysRoundBothCorners;
     private Path mTmpPath = new Path();
-    private float mCurrentBottomRoundness;
-    private float mCurrentTopRoundness;
-    private float mBottomRoundness;
-    private float mTopRoundness;
     private int mBackgroundTop;
 
     /**
@@ -80,8 +55,7 @@
     private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
         @Override
         public void getOutline(View view, Outline outline) {
-            if (!mCustomOutline && getCurrentTopRoundness() == 0.0f
-                    && getCurrentBottomRoundness() == 0.0f && !mAlwaysRoundBothCorners) {
+            if (!mCustomOutline && !hasRoundedCorner() && !mAlwaysRoundBothCorners) {
                 // Only when translating just the contents, does the outline need to be shifted.
                 int translation = !mDismissUsingRowTranslationX ? (int) getTranslation() : 0;
                 int left = Math.max(translation, 0);
@@ -99,14 +73,18 @@
         }
     };
 
+    @Override
+    public RoundableState getRoundableState() {
+        return mRoundableState;
+    }
+
     protected Path getClipPath(boolean ignoreTranslation) {
         int left;
         int top;
         int right;
         int bottom;
         int height;
-        float topRoundness = mAlwaysRoundBothCorners
-                ? mOutlineRadius : getCurrentBackgroundRadiusTop();
+        float topRoundness = mAlwaysRoundBothCorners ? getMaxRadius() : getTopCornerRadius();
         if (!mCustomOutline) {
             // The outline just needs to be shifted if we're translating the contents. Otherwise
             // it's already in the right place.
@@ -130,12 +108,11 @@
         if (height == 0) {
             return EMPTY_PATH;
         }
-        float bottomRoundness = mAlwaysRoundBothCorners
-                ? mOutlineRadius : getCurrentBackgroundRadiusBottom();
+        float bottomRoundness = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius();
         if (topRoundness + bottomRoundness > height) {
             float overShoot = topRoundness + bottomRoundness - height;
-            float currentTopRoundness = getCurrentTopRoundness();
-            float currentBottomRoundness = getCurrentBottomRoundness();
+            float currentTopRoundness = getTopRoundness();
+            float currentBottomRoundness = getBottomRoundness();
             topRoundness -= overShoot * currentTopRoundness
                     / (currentTopRoundness + currentBottomRoundness);
             bottomRoundness -= overShoot * currentBottomRoundness
@@ -145,8 +122,18 @@
         return mTmpPath;
     }
 
-    public void getRoundedRectPath(int left, int top, int right, int bottom,
-            float topRoundness, float bottomRoundness, Path outPath) {
+    /**
+     * Add a round rect in {@code outPath}
+     * @param outPath destination path
+     */
+    public void getRoundedRectPath(
+            int left,
+            int top,
+            int right,
+            int bottom,
+            float topRoundness,
+            float bottomRoundness,
+            Path outPath) {
         outPath.reset();
         mTmpCornerRadii[0] = topRoundness;
         mTmpCornerRadii[1] = topRoundness;
@@ -168,15 +155,28 @@
     @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
         canvas.save();
+        Path clipPath = null;
+        Path childClipPath = null;
         if (childNeedsClipping(child)) {
-            Path clipPath = getCustomClipPath(child);
+            clipPath = getCustomClipPath(child);
             if (clipPath == null) {
                 clipPath = getClipPath(false /* ignoreTranslation */);
             }
-            if (clipPath != null) {
-                canvas.clipPath(clipPath);
+            // If the notification uses "RowTranslationX" as dismiss behavior, we should clip the
+            // children instead.
+            if (mDismissUsingRowTranslationX && child instanceof NotificationChildrenContainer) {
+                childClipPath = clipPath;
+                clipPath = null;
             }
         }
+
+        if (child instanceof NotificationChildrenContainer) {
+            ((NotificationChildrenContainer) child).setChildClipPath(childClipPath);
+        }
+        if (clipPath != null) {
+            canvas.clipPath(clipPath);
+        }
+
         boolean result = super.drawChild(canvas, child, drawingTime);
         canvas.restore();
         return result;
@@ -207,73 +207,21 @@
 
     private void initDimens() {
         Resources res = getResources();
-        mOutlineRadius = res.getDimension(R.dimen.notification_shadow_radius);
         mAlwaysRoundBothCorners = res.getBoolean(R.bool.config_clipNotificationsToOutline);
-        if (!mAlwaysRoundBothCorners) {
-            mOutlineRadius = res.getDimensionPixelSize(R.dimen.notification_corner_radius);
+        float maxRadius;
+        if (mAlwaysRoundBothCorners) {
+            maxRadius = res.getDimension(R.dimen.notification_shadow_radius);
+        } else {
+            maxRadius = res.getDimensionPixelSize(R.dimen.notification_corner_radius);
         }
+        mRoundableState = new RoundableState(this, this, maxRadius);
         setClipToOutline(mAlwaysRoundBothCorners);
     }
 
     @Override
-    public boolean setTopRoundness(float topRoundness, boolean animate) {
-        if (mTopRoundness != topRoundness) {
-            float diff = Math.abs(topRoundness - mTopRoundness);
-            mTopRoundness = topRoundness;
-            boolean shouldAnimate = animate;
-            if (PropertyAnimator.isAnimating(this, TOP_ROUNDNESS) && diff > 0.5f) {
-                // Fail safe:
-                // when we've been animating previously and we're now getting an update in the
-                // other direction, make sure to animate it too, otherwise, the localized updating
-                // may make the start larger than 1.0.
-                shouldAnimate = true;
-            }
-            PropertyAnimator.setProperty(this, TOP_ROUNDNESS, topRoundness,
-                    ROUNDNESS_PROPERTIES, shouldAnimate);
-            return true;
-        }
-        return false;
-    }
-
-    protected void applyRoundness() {
+    public void applyRoundness() {
         invalidateOutline();
-        invalidate();
-    }
-
-    public float getCurrentBackgroundRadiusTop() {
-        return getCurrentTopRoundness() * mOutlineRadius;
-    }
-
-    public float getCurrentTopRoundness() {
-        return mCurrentTopRoundness;
-    }
-
-    public float getCurrentBottomRoundness() {
-        return mCurrentBottomRoundness;
-    }
-
-    public float getCurrentBackgroundRadiusBottom() {
-        return getCurrentBottomRoundness() * mOutlineRadius;
-    }
-
-    @Override
-    public boolean setBottomRoundness(float bottomRoundness, boolean animate) {
-        if (mBottomRoundness != bottomRoundness) {
-            float diff = Math.abs(bottomRoundness - mBottomRoundness);
-            mBottomRoundness = bottomRoundness;
-            boolean shouldAnimate = animate;
-            if (PropertyAnimator.isAnimating(this, BOTTOM_ROUNDNESS) && diff > 0.5f) {
-                // Fail safe:
-                // when we've been animating previously and we're now getting an update in the
-                // other direction, make sure to animate it too, otherwise, the localized updating
-                // may make the start larger than 1.0.
-                shouldAnimate = true;
-            }
-            PropertyAnimator.setProperty(this, BOTTOM_ROUNDNESS, bottomRoundness,
-                    ROUNDNESS_PROPERTIES, shouldAnimate);
-            return true;
-        }
-        return false;
+        super.applyRoundness();
     }
 
     protected void setBackgroundTop(int backgroundTop) {
@@ -283,16 +231,6 @@
         }
     }
 
-    private void setTopRoundnessInternal(float topRoundness) {
-        mCurrentTopRoundness = topRoundness;
-        applyRoundness();
-    }
-
-    private void setBottomRoundnessInternal(float bottomRoundness) {
-        mCurrentBottomRoundness = bottomRoundness;
-        applyRoundness();
-    }
-
     public void onDensityOrFontScaleChanged() {
         initDimens();
         applyRoundness();
@@ -348,9 +286,10 @@
 
     /**
      * Set the dismiss behavior of the view.
+     *
      * @param usingRowTranslationX {@code true} if the view should translate using regular
-     *                                          translationX, otherwise the contents will be
-     *                                          translated.
+     *                             translationX, otherwise the contents will be
+     *                             translated.
      */
     public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) {
         mDismissUsingRowTranslationX = usingRowTranslationX;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 38f0c55..955d7c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -36,6 +36,8 @@
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.notification.Roundable;
+import com.android.systemui.statusbar.notification.RoundableState;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.util.DumpUtilsKt;
@@ -47,9 +49,10 @@
 /**
  * An abstract view for expandable views.
  */
-public abstract class ExpandableView extends FrameLayout implements Dumpable {
+public abstract class ExpandableView extends FrameLayout implements Dumpable, Roundable {
     private static final String TAG = "ExpandableView";
 
+    private RoundableState mRoundableState = null;
     protected OnHeightChangedListener mOnHeightChangedListener;
     private int mActualHeight;
     protected int mClipTopAmount;
@@ -78,6 +81,14 @@
         initDimens();
     }
 
+    @Override
+    public RoundableState getRoundableState() {
+        if (mRoundableState == null) {
+            mRoundableState = new RoundableState(this, this, 0f);
+        }
+        return mRoundableState;
+    }
+
     private void initDimens() {
         mContentShift = getResources().getDimensionPixelSize(
                 R.dimen.shelf_transform_content_shift);
@@ -440,8 +451,7 @@
             int top = getClipTopAmount();
             int bottom = Math.max(Math.max(getActualHeight() + getExtraBottomPadding()
                     - mClipBottomAmount, top), mMinimumHeightForClipping);
-            int halfExtraWidth = (int) (mExtraWidthForClipping / 2.0f);
-            mClipRect.set(-halfExtraWidth, top, getWidth() + halfExtraWidth, bottom);
+            mClipRect.set(Integer.MIN_VALUE, top, Integer.MAX_VALUE, bottom);
             setClipBounds(mClipRect);
         } else {
             setClipBounds(null);
@@ -455,7 +465,6 @@
 
     public void setExtraWidthForClipping(float extraWidthForClipping) {
         mExtraWidthForClipping = extraWidthForClipping;
-        updateClipping();
     }
 
     public float getHeaderVisibleAmount() {
@@ -844,22 +853,6 @@
         return mFirstInSection;
     }
 
-    /**
-     * Set the topRoundness of this view.
-     * @return Whether the roundness was changed.
-     */
-    public boolean setTopRoundness(float topRoundness, boolean animate) {
-        return false;
-    }
-
-    /**
-     * Set the bottom roundness of this view.
-     * @return Whether the roundness was changed.
-     */
-    public boolean setBottomRoundness(float bottomRoundness, boolean animate) {
-        return false;
-    }
-
     public int getHeadsUpHeightWithoutHeader() {
         return getHeight();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
index ab91926..46fef3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.notification.row
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
 import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.INFO
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
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 4c69304..c534860 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
@@ -40,7 +40,7 @@
 import com.android.internal.widget.ImageMessageConsumer;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.media.MediaFeatureFlag;
+import com.android.systemui.media.controls.util.MediaFeatureFlag;
 import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
index f9923b2..8a5d29a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.notification.row
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
 import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.INFO
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 7a65436..f13e48d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -35,12 +35,15 @@
 
 import com.android.internal.widget.CachingIconView;
 import com.android.internal.widget.NotificationExpandButton;
+import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.TransformableView;
 import com.android.systemui.statusbar.ViewTransformationHelper;
 import com.android.systemui.statusbar.notification.CustomInterpolatorTransformation;
 import com.android.systemui.statusbar.notification.FeedbackIcon;
 import com.android.systemui.statusbar.notification.ImageTransformState;
+import com.android.systemui.statusbar.notification.Roundable;
+import com.android.systemui.statusbar.notification.RoundableState;
 import com.android.systemui.statusbar.notification.TransformState;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
@@ -49,13 +52,12 @@
 /**
  * Wraps a notification view which may or may not include a header.
  */
-public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
+public class NotificationHeaderViewWrapper extends NotificationViewWrapper implements Roundable {
 
+    private final RoundableState mRoundableState;
     private static final Interpolator LOW_PRIORITY_HEADER_CLOSE
             = new PathInterpolator(0.4f, 0f, 0.7f, 1f);
-
     protected final ViewTransformationHelper mTransformationHelper;
-
     private CachingIconView mIcon;
     private NotificationExpandButton mExpandButton;
     private View mAltExpandTarget;
@@ -67,12 +69,16 @@
     private ImageView mWorkProfileImage;
     private View mAudiblyAlertedIcon;
     private View mFeedbackIcon;
-
     private boolean mIsLowPriority;
     private boolean mTransformLowPriorityTitle;
 
     protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
         super(ctx, view, row);
+        mRoundableState = new RoundableState(
+                mView,
+                this,
+                ctx.getResources().getDimension(R.dimen.notification_corner_radius)
+        );
         mTransformationHelper = new ViewTransformationHelper();
 
         // we want to avoid that the header clashes with the other text when transforming
@@ -81,7 +87,8 @@
                 new CustomInterpolatorTransformation(TRANSFORMING_VIEW_TITLE) {
 
                     @Override
-                    public Interpolator getCustomInterpolator(int interpolationType,
+                    public Interpolator getCustomInterpolator(
+                            int interpolationType,
                             boolean isFrom) {
                         boolean isLowPriority = mView instanceof NotificationHeaderView;
                         if (interpolationType == TRANSFORM_Y) {
@@ -99,11 +106,17 @@
                     protected boolean hasCustomTransformation() {
                         return mIsLowPriority && mTransformLowPriorityTitle;
                     }
-                }, TRANSFORMING_VIEW_TITLE);
+                },
+                TRANSFORMING_VIEW_TITLE);
         resolveHeaderViews();
         addFeedbackOnClickListener(row);
     }
 
+    @Override
+    public RoundableState getRoundableState() {
+        return mRoundableState;
+    }
+
     protected void resolveHeaderViews() {
         mIcon = mView.findViewById(com.android.internal.R.id.icon);
         mHeaderText = mView.findViewById(com.android.internal.R.id.header_text);
@@ -128,7 +141,9 @@
         }
     }
 
-    /** Shows the given feedback icon, or hides the icon if null. */
+    /**
+     * Shows the given feedback icon, or hides the icon if null.
+     */
     @Override
     public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
         if (mFeedbackIcon != null) {
@@ -193,7 +208,7 @@
                     // its animation
                     && child.getId() != com.android.internal.R.id.conversation_icon_badge_ring) {
                 ((ImageView) child).setCropToPadding(true);
-            } else if (child instanceof ViewGroup){
+            } else if (child instanceof ViewGroup) {
                 ViewGroup group = (ViewGroup) child;
                 for (int i = 0; i < group.getChildCount(); i++) {
                     stack.push(group.getChildAt(i));
@@ -215,7 +230,9 @@
     }
 
     @Override
-    public void updateExpandability(boolean expandable, View.OnClickListener onClickListener,
+    public void updateExpandability(
+            boolean expandable,
+            View.OnClickListener onClickListener,
             boolean requestLayout) {
         mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
         mExpandButton.setOnClickListener(expandable ? onClickListener : null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 2719dd8..b2628e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -142,6 +142,11 @@
      */
     private boolean mIsFlingRequiredAfterLockScreenSwipeUp = false;
 
+    /**
+     * Whether the shade is currently closing.
+     */
+    private boolean mIsClosing;
+
     @VisibleForTesting
     public boolean isFlingRequiredAfterLockScreenSwipeUp() {
         return mIsFlingRequiredAfterLockScreenSwipeUp;
@@ -717,6 +722,20 @@
                 && mStatusBarKeyguardViewManager.isBouncerInTransit();
     }
 
+    /**
+     * @param isClosing Whether the shade is currently closing.
+     */
+    public void setIsClosing(boolean isClosing) {
+        mIsClosing = isClosing;
+    }
+
+    /**
+     * @return Whether the shade is currently closing.
+     */
+    public boolean isClosing() {
+        return mIsClosing;
+    }
+
     @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.println("mTopPadding=" + mTopPadding);
@@ -761,5 +780,6 @@
                 + mIsFlingRequiredAfterLockScreenSwipeUp);
         pw.println("mZDistanceBetweenElements=" + mZDistanceBetweenElements);
         pw.println("mBaseZHeight=" + mBaseZHeight);
+        pw.println("mIsClosing=" + mIsClosing);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 0dda263..26f0ad9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -21,6 +21,9 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.Path.Direction;
 import android.graphics.drawable.ColorDrawable;
 import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
@@ -33,6 +36,7 @@
 import android.widget.RemoteViews;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -43,10 +47,14 @@
 import com.android.systemui.statusbar.notification.FeedbackIcon;
 import com.android.systemui.statusbar.notification.NotificationFadeAware;
 import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.Roundable;
+import com.android.systemui.statusbar.notification.RoundableState;
+import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.HybridGroupManager;
 import com.android.systemui.statusbar.notification.row.HybridNotificationView;
+import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
 
 import java.util.ArrayList;
@@ -56,7 +64,7 @@
  * A container containing child notifications
  */
 public class NotificationChildrenContainer extends ViewGroup
-        implements NotificationFadeAware {
+        implements NotificationFadeAware, Roundable {
 
     private static final String TAG = "NotificationChildrenContainer";
 
@@ -100,9 +108,9 @@
     private boolean mEnableShadowOnChildNotifications;
 
     private NotificationHeaderView mNotificationHeader;
-    private NotificationViewWrapper mNotificationHeaderWrapper;
+    private NotificationHeaderViewWrapper mNotificationHeaderWrapper;
     private NotificationHeaderView mNotificationHeaderLowPriority;
-    private NotificationViewWrapper mNotificationHeaderWrapperLowPriority;
+    private NotificationHeaderViewWrapper mNotificationHeaderWrapperLowPriority;
     private NotificationGroupingUtil mGroupingUtil;
     private ViewState mHeaderViewState;
     private int mClipBottomAmount;
@@ -110,7 +118,8 @@
     private OnClickListener mHeaderClickListener;
     private ViewGroup mCurrentHeader;
     private boolean mIsConversation;
-
+    private Path mChildClipPath = null;
+    private final Path mHeaderPath = new Path();
     private boolean mShowGroupCountInExpander;
     private boolean mShowDividersWhenExpanded;
     private boolean mHideDividersDuringExpand;
@@ -119,6 +128,8 @@
     private float mHeaderVisibleAmount = 1.0f;
     private int mUntruncatedChildCount;
     private boolean mContainingNotificationIsFaded = false;
+    private RoundableState mRoundableState;
+    private boolean mIsNotificationGroupCornerEnabled;
 
     public NotificationChildrenContainer(Context context) {
         this(context, null);
@@ -132,10 +143,14 @@
         this(context, attrs, defStyleAttr, 0);
     }
 
-    public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr,
+    public NotificationChildrenContainer(
+            Context context,
+            AttributeSet attrs,
+            int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         mHybridGroupManager = new HybridGroupManager(getContext());
+        mRoundableState = new RoundableState(this, this, 0f);
         initDimens();
         setClipChildren(false);
     }
@@ -167,6 +182,12 @@
         mHybridGroupManager.initDimens();
     }
 
+    @NonNull
+    @Override
+    public RoundableState getRoundableState() {
+        return mRoundableState;
+    }
+
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         int childCount =
@@ -271,7 +292,7 @@
     /**
      * Add a child notification to this view.
      *
-     * @param row the row to add
+     * @param row        the row to add
      * @param childIndex the index to add it at, if -1 it will be added at the end
      */
     public void addNotification(ExpandableNotificationRow row, int childIndex) {
@@ -347,8 +368,11 @@
             mNotificationHeader.findViewById(com.android.internal.R.id.expand_button)
                     .setVisibility(VISIBLE);
             mNotificationHeader.setOnClickListener(mHeaderClickListener);
-            mNotificationHeaderWrapper = NotificationViewWrapper.wrap(getContext(),
-                    mNotificationHeader, mContainingNotification);
+            mNotificationHeaderWrapper =
+                    (NotificationHeaderViewWrapper) NotificationViewWrapper.wrap(
+                            getContext(),
+                            mNotificationHeader,
+                            mContainingNotification);
             addView(mNotificationHeader, 0);
             invalidate();
         } else {
@@ -381,8 +405,11 @@
                 mNotificationHeaderLowPriority.findViewById(com.android.internal.R.id.expand_button)
                         .setVisibility(VISIBLE);
                 mNotificationHeaderLowPriority.setOnClickListener(mHeaderClickListener);
-                mNotificationHeaderWrapperLowPriority = NotificationViewWrapper.wrap(getContext(),
-                        mNotificationHeaderLowPriority, mContainingNotification);
+                mNotificationHeaderWrapperLowPriority =
+                        (NotificationHeaderViewWrapper) NotificationViewWrapper.wrap(
+                                getContext(),
+                                mNotificationHeaderLowPriority,
+                                mContainingNotification);
                 addView(mNotificationHeaderLowPriority, 0);
                 invalidate();
             } else {
@@ -462,20 +489,8 @@
     }
 
     /**
-     * Sets the alpha on the content, while leaving the background of the container itself as is.
-     *
-     * @param alpha alpha value to apply to the content
+     * To be called any time the rows have been updated
      */
-    public void setContentAlpha(float alpha) {
-        for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
-            mNotificationHeader.getChildAt(i).setAlpha(alpha);
-        }
-        for (ExpandableNotificationRow child : getAttachedChildren()) {
-            child.setContentAlpha(alpha);
-        }
-    }
-
-    /** To be called any time the rows have been updated */
     public void updateExpansionStates() {
         if (mChildrenExpanded || mUserLocked) {
             // we don't modify it the group is expanded or if we are expanding it
@@ -489,7 +504,6 @@
     }
 
     /**
-     *
      * @return the intrinsic size of this children container, i.e the natural fully expanded state
      */
     public int getIntrinsicHeight() {
@@ -499,7 +513,7 @@
 
     /**
      * @return the intrinsic height with a number of children given
-     *         in @param maxAllowedVisibleChildren
+     * in @param maxAllowedVisibleChildren
      */
     private int getIntrinsicHeight(float maxAllowedVisibleChildren) {
         if (showingAsLowPriority()) {
@@ -553,7 +567,8 @@
 
     /**
      * Update the state of all its children based on a linear layout algorithm.
-     * @param parentState the state of the parent
+     *
+     * @param parentState  the state of the parent
      * @param ambientState the ambient state containing ambient information
      */
     public void updateState(ExpandableViewState parentState, AmbientState ambientState) {
@@ -669,14 +684,17 @@
      * When moving into the bottom stack, the bottom visible child in an expanded group adjusts its
      * height, children in the group after this are gone.
      *
-     * @param child the child who's height to adjust.
+     * @param child        the child who's height to adjust.
      * @param parentHeight the height of the parent.
-     * @param childState the state to update.
-     * @param yPosition the yPosition of the view.
+     * @param childState   the state to update.
+     * @param yPosition    the yPosition of the view.
      * @return true if children after this one should be hidden.
      */
-    private boolean updateChildStateForExpandedGroup(ExpandableNotificationRow child,
-            int parentHeight, ExpandableViewState childState, int yPosition) {
+    private boolean updateChildStateForExpandedGroup(
+            ExpandableNotificationRow child,
+            int parentHeight,
+            ExpandableViewState childState,
+            int yPosition) {
         final int top = yPosition + child.getClipTopAmount();
         final int intrinsicHeight = child.getIntrinsicHeight();
         final int bottom = top + intrinsicHeight;
@@ -704,13 +722,15 @@
         if (mIsLowPriority
                 || (!mContainingNotification.isOnKeyguard() && mContainingNotification.isExpanded())
                 || (mContainingNotification.isHeadsUpState()
-                        && mContainingNotification.canShowHeadsUp())) {
+                && mContainingNotification.canShowHeadsUp())) {
             return NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED;
         }
         return NUMBER_OF_CHILDREN_WHEN_COLLAPSED;
     }
 
-    /** Applies state to children. */
+    /**
+     * Applies state to children.
+     */
     public void applyState() {
         int childCount = mAttachedChildren.size();
         ViewState tmpState = new ViewState();
@@ -782,17 +802,73 @@
         }
     }
 
+    @Override
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        boolean isCanvasChanged = false;
+
+        Path clipPath = mChildClipPath;
+        if (clipPath != null) {
+            final float translation;
+            if (child instanceof ExpandableNotificationRow) {
+                ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) child;
+                translation = notificationRow.getTranslation();
+            } else {
+                translation = child.getTranslationX();
+            }
+
+            isCanvasChanged = true;
+            canvas.save();
+            if (mIsNotificationGroupCornerEnabled && translation != 0f) {
+                clipPath.offset(translation, 0f);
+                canvas.clipPath(clipPath);
+                clipPath.offset(-translation, 0f);
+            } else {
+                canvas.clipPath(clipPath);
+            }
+        }
+
+        if (child instanceof NotificationHeaderView
+                && mNotificationHeaderWrapper.hasRoundedCorner()) {
+            float[] radii = mNotificationHeaderWrapper.getUpdatedRadii();
+            mHeaderPath.reset();
+            mHeaderPath.addRoundRect(
+                    child.getLeft(),
+                    child.getTop(),
+                    child.getRight(),
+                    child.getBottom(),
+                    radii,
+                    Direction.CW
+            );
+            if (!isCanvasChanged) {
+                isCanvasChanged = true;
+                canvas.save();
+            }
+            canvas.clipPath(mHeaderPath);
+        }
+
+        if (isCanvasChanged) {
+            boolean result = super.drawChild(canvas, child, drawingTime);
+            canvas.restore();
+            return result;
+        } else {
+            // If there have been no changes to the canvas we can proceed as usual
+            return super.drawChild(canvas, child, drawingTime);
+        }
+    }
+
+
     /**
      * This is called when the children expansion has changed and positions the children properly
      * for an appear animation.
-     *
      */
     public void prepareExpansionChanged() {
         // TODO: do something that makes sense, like placing the invisible views correctly
         return;
     }
 
-    /** Animate to a given state. */
+    /**
+     * Animate to a given state.
+     */
     public void startAnimationToState(AnimationProperties properties) {
         int childCount = mAttachedChildren.size();
         ViewState tmpState = new ViewState();
@@ -1116,7 +1192,8 @@
      * Get the minimum Height for this group.
      *
      * @param maxAllowedVisibleChildren the number of children that should be visible
-     * @param likeHighPriority if the height should be calculated as if it were not low priority
+     * @param likeHighPriority          if the height should be calculated as if it were not low
+     *                                  priority
      */
     private int getMinHeight(int maxAllowedVisibleChildren, boolean likeHighPriority) {
         return getMinHeight(maxAllowedVisibleChildren, likeHighPriority, mCurrentHeaderTranslation);
@@ -1126,10 +1203,13 @@
      * Get the minimum Height for this group.
      *
      * @param maxAllowedVisibleChildren the number of children that should be visible
-     * @param likeHighPriority if the height should be calculated as if it were not low priority
-     * @param headerTranslation the translation amount of the header
+     * @param likeHighPriority          if the height should be calculated as if it were not low
+     *                                  priority
+     * @param headerTranslation         the translation amount of the header
      */
-    private int getMinHeight(int maxAllowedVisibleChildren, boolean likeHighPriority,
+    private int getMinHeight(
+            int maxAllowedVisibleChildren,
+            boolean likeHighPriority,
             int headerTranslation) {
         if (!likeHighPriority && showingAsLowPriority()) {
             if (mNotificationHeaderLowPriority == null) {
@@ -1288,16 +1368,19 @@
         return mUserLocked;
     }
 
-    public void setCurrentBottomRoundness(float currentBottomRoundness) {
+    @Override
+    public void applyRoundness() {
+        Roundable.super.applyRoundness();
         boolean last = true;
         for (int i = mAttachedChildren.size() - 1; i >= 0; i--) {
             ExpandableNotificationRow child = mAttachedChildren.get(i);
             if (child.getVisibility() == View.GONE) {
                 continue;
             }
-            float bottomRoundness = last ? currentBottomRoundness : 0.0f;
-            child.setBottomRoundness(bottomRoundness, isShown() /* animate */);
-            child.setTopRoundness(0.0f, false /* animate */);
+            child.requestBottomRoundness(
+                    last ? getBottomRoundness() : 0f,
+                    /* animate = */ isShown(),
+                    SourceType.DefaultValue);
             last = false;
         }
     }
@@ -1307,7 +1390,9 @@
         mCurrentHeaderTranslation = (int) ((1.0f - headerVisibleAmount) * mTranslationForHeader);
     }
 
-    /** Shows the given feedback icon, or hides the icon if null. */
+    /**
+     * Shows the given feedback icon, or hides the icon if null.
+     */
     public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
         if (mNotificationHeaderWrapper != null) {
             mNotificationHeaderWrapper.setFeedbackIcon(icon);
@@ -1339,4 +1424,26 @@
             child.setNotificationFaded(faded);
         }
     }
+
+    /**
+     * Allow to define a path the clip the children in #drawChild()
+     *
+     * @param childClipPath path used to clip the children
+     */
+    public void setChildClipPath(@Nullable Path childClipPath) {
+        mChildClipPath = childClipPath;
+        invalidate();
+    }
+
+    public NotificationHeaderViewWrapper getNotificationHeaderWrapper() {
+        return mNotificationHeaderWrapper;
+    }
+
+    /**
+     * Enable the support for rounded corner in notification group
+     * @param enabled true if is supported
+     */
+    public void enableNotificationGroupCorner(boolean enabled) {
+        mIsNotificationGroupCornerEnabled = enabled;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 2015c87..6810055 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -26,6 +26,8 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
+import com.android.systemui.statusbar.notification.Roundable;
+import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotificationRoundnessLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -59,8 +61,8 @@
     private boolean mIsClearAllInProgress;
 
     private ExpandableView mSwipedView = null;
-    private ExpandableView mViewBeforeSwipedView = null;
-    private ExpandableView mViewAfterSwipedView = null;
+    private Roundable mViewBeforeSwipedView = null;
+    private Roundable mViewAfterSwipedView = null;
 
     @Inject
     NotificationRoundnessManager(
@@ -101,11 +103,12 @@
     public boolean isViewAffectedBySwipe(ExpandableView expandableView) {
         return expandableView != null
                 && (expandableView == mSwipedView
-                    || expandableView == mViewBeforeSwipedView
-                    || expandableView == mViewAfterSwipedView);
+                || expandableView == mViewBeforeSwipedView
+                || expandableView == mViewAfterSwipedView);
     }
 
-    boolean updateViewWithoutCallback(ExpandableView view,
+    boolean updateViewWithoutCallback(
+            ExpandableView view,
             boolean animate) {
         if (view == null
                 || view == mViewBeforeSwipedView
@@ -113,11 +116,15 @@
             return false;
         }
 
-        final float topRoundness = getRoundnessFraction(view, true /* top */);
-        final float bottomRoundness = getRoundnessFraction(view, false /* top */);
+        final boolean isTopChanged = view.requestTopRoundness(
+                getRoundnessDefaultValue(view, true /* top */),
+                animate,
+                SourceType.DefaultValue);
 
-        final boolean topChanged = view.setTopRoundness(topRoundness, animate);
-        final boolean bottomChanged = view.setBottomRoundness(bottomRoundness, animate);
+        final boolean isBottomChanged = view.requestBottomRoundness(
+                getRoundnessDefaultValue(view, /* top = */ false),
+                animate,
+                SourceType.DefaultValue);
 
         final boolean isFirstInSection = isFirstInSection(view);
         final boolean isLastInSection = isLastInSection(view);
@@ -126,9 +133,9 @@
         view.setLastInSection(isLastInSection);
 
         mNotifLogger.onCornersUpdated(view, isFirstInSection,
-                isLastInSection, topChanged, bottomChanged);
+                isLastInSection, isTopChanged, isBottomChanged);
 
-        return (isFirstInSection || isLastInSection) && (topChanged || bottomChanged);
+        return (isFirstInSection || isLastInSection) && (isTopChanged || isBottomChanged);
     }
 
     private boolean isFirstInSection(ExpandableView view) {
@@ -150,42 +157,46 @@
     }
 
     void setViewsAffectedBySwipe(
-            ExpandableView viewBefore,
+            Roundable viewBefore,
             ExpandableView viewSwiped,
-            ExpandableView viewAfter) {
+            Roundable viewAfter) {
         final boolean animate = true;
+        final SourceType source = SourceType.OnDismissAnimation;
 
-        ExpandableView oldViewBefore = mViewBeforeSwipedView;
+        // This method requires you to change the roundness of the current View targets and reset
+        // the roundness of the old View targets (if any) to 0f.
+        // To avoid conflicts, it generates a set of old Views and removes the current Views
+        // from this set.
+        HashSet<Roundable> oldViews = new HashSet<>();
+        if (mViewBeforeSwipedView != null) oldViews.add(mViewBeforeSwipedView);
+        if (mSwipedView != null) oldViews.add(mSwipedView);
+        if (mViewAfterSwipedView != null) oldViews.add(mViewAfterSwipedView);
+
         mViewBeforeSwipedView = viewBefore;
-        if (oldViewBefore != null) {
-            final float bottomRoundness = getRoundnessFraction(oldViewBefore, false /* top */);
-            oldViewBefore.setBottomRoundness(bottomRoundness,  animate);
-        }
         if (viewBefore != null) {
-            viewBefore.setBottomRoundness(1f, animate);
+            oldViews.remove(viewBefore);
+            viewBefore.requestTopRoundness(0f, animate, source);
+            viewBefore.requestBottomRoundness(1f, animate, source);
         }
 
-        ExpandableView oldSwipedview = mSwipedView;
         mSwipedView = viewSwiped;
-        if (oldSwipedview != null) {
-            final float bottomRoundness = getRoundnessFraction(oldSwipedview, false /* top */);
-            final float topRoundness = getRoundnessFraction(oldSwipedview, true /* top */);
-            oldSwipedview.setTopRoundness(topRoundness, animate);
-            oldSwipedview.setBottomRoundness(bottomRoundness, animate);
-        }
         if (viewSwiped != null) {
-            viewSwiped.setTopRoundness(1f, animate);
-            viewSwiped.setBottomRoundness(1f, animate);
+            oldViews.remove(viewSwiped);
+            viewSwiped.requestTopRoundness(1f, animate, source);
+            viewSwiped.requestBottomRoundness(1f, animate, source);
         }
 
-        ExpandableView oldViewAfter = mViewAfterSwipedView;
         mViewAfterSwipedView = viewAfter;
-        if (oldViewAfter != null) {
-            final float topRoundness = getRoundnessFraction(oldViewAfter, true /* top */);
-            oldViewAfter.setTopRoundness(topRoundness, animate);
-        }
         if (viewAfter != null) {
-            viewAfter.setTopRoundness(1f, animate);
+            oldViews.remove(viewAfter);
+            viewAfter.requestTopRoundness(1f, animate, source);
+            viewAfter.requestBottomRoundness(0f, animate, source);
+        }
+
+        // After setting the current Views, reset the views that are still present in the set.
+        for (Roundable oldView : oldViews) {
+            oldView.requestTopRoundness(0f, animate, source);
+            oldView.requestBottomRoundness(0f, animate, source);
         }
     }
 
@@ -193,7 +204,7 @@
         mIsClearAllInProgress = isClearingAll;
     }
 
-    private float getRoundnessFraction(ExpandableView view, boolean top) {
+    private float getRoundnessDefaultValue(Roundable view, boolean top) {
         if (view == null) {
             return 0f;
         }
@@ -207,28 +218,35 @@
                 && mIsClearAllInProgress) {
             return 1.0f;
         }
-        if ((view.isPinned()
-                || (view.isHeadsUpAnimatingAway()) && !mExpanded)) {
-            return 1.0f;
-        }
-        if (isFirstInSection(view) && top) {
-            return 1.0f;
-        }
-        if (isLastInSection(view) && !top) {
-            return 1.0f;
-        }
+        if (view instanceof ExpandableView) {
+            ExpandableView expandableView = (ExpandableView) view;
+            if ((expandableView.isPinned()
+                    || (expandableView.isHeadsUpAnimatingAway()) && !mExpanded)) {
+                return 1.0f;
+            }
+            if (isFirstInSection(expandableView) && top) {
+                return 1.0f;
+            }
+            if (isLastInSection(expandableView) && !top) {
+                return 1.0f;
+            }
 
-        if (view == mTrackedHeadsUp) {
-            // If we're pushing up on a headsup the appear fraction is < 0 and it needs to still be
-            // rounded.
-            return MathUtils.saturate(1.0f - mAppearFraction);
+            if (view == mTrackedHeadsUp) {
+                // If we're pushing up on a headsup the appear fraction is < 0 and it needs to
+                // still be rounded.
+                return MathUtils.saturate(1.0f - mAppearFraction);
+            }
+            if (expandableView.showingPulsing() && mRoundForPulsingViews) {
+                return 1.0f;
+            }
+            if (expandableView.isChildInGroup()) {
+                return 0f;
+            }
+            final Resources resources = expandableView.getResources();
+            return resources.getDimension(R.dimen.notification_corner_radius_small)
+                    / resources.getDimension(R.dimen.notification_corner_radius);
         }
-        if (view.showingPulsing() && mRoundForPulsingViews) {
-            return 1.0f;
-        }
-        final Resources resources = view.getResources();
-        return resources.getDimension(R.dimen.notification_corner_radius_small)
-                / resources.getDimension(R.dimen.notification_corner_radius);
+        return 0f;
     }
 
     public void setExpanded(float expandedHeight, float appearFraction) {
@@ -258,8 +276,10 @@
         mNotifLogger.onSectionCornersUpdated(sections, anyChanged);
     }
 
-    private boolean handleRemovedOldViews(NotificationSection[] sections,
-            ExpandableView[] oldViews, boolean first) {
+    private boolean handleRemovedOldViews(
+            NotificationSection[] sections,
+            ExpandableView[] oldViews,
+            boolean first) {
         boolean anyChanged = false;
         for (ExpandableView oldView : oldViews) {
             if (oldView != null) {
@@ -289,8 +309,10 @@
         return anyChanged;
     }
 
-    private boolean handleAddedNewViews(NotificationSection[] sections,
-            ExpandableView[] oldViews, boolean first) {
+    private boolean handleAddedNewViews(
+            NotificationSection[] sections,
+            ExpandableView[] oldViews,
+            boolean first) {
         boolean anyChanged = false;
         for (NotificationSection section : sections) {
             ExpandableView newView =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
index cb7dfe8..b61c55e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
@@ -17,9 +17,9 @@
 package com.android.systemui.statusbar.notification.stack
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.NotificationSectionLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
 import javax.inject.Inject
 
 private const val TAG = "NotifSections"
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 91a2813..a1b77ac 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,11 +19,10 @@
 import android.util.Log
 import android.view.View
 import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.media.KeyguardMediaController
+import com.android.systemui.media.controls.ui.KeyguardMediaController
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
 import com.android.systemui.statusbar.notification.collection.render.MediaContainerController
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
-import com.android.systemui.statusbar.notification.collection.render.ShadeViewManager
 import com.android.systemui.statusbar.notification.dagger.AlertingHeader
 import com.android.systemui.statusbar.notification.dagger.IncomingHeader
 import com.android.systemui.statusbar.notification.dagger.PeopleHeader
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 55c577f..df705c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -255,7 +255,6 @@
     private boolean mClearAllInProgress;
     private FooterClearAllListener mFooterClearAllListener;
     private boolean mFlingAfterUpEvent;
-
     /**
      * Was the scroller scrolled to the top when the down motion was observed?
      */
@@ -1189,7 +1188,7 @@
             return;
         }
         for (int i = 0; i < getChildCount(); i++) {
-            ExpandableView child = (ExpandableView) getChildAt(i);
+            ExpandableView child = getChildAtIndex(i);
             if (mChildrenToAddAnimated.contains(child)) {
                 final int startingPosition = getPositionInLinearLayout(child);
                 final int childHeight = getIntrinsicHeight(child) + mPaddingBetweenElements;
@@ -1659,7 +1658,7 @@
         // find the view under the pointer, accounting for GONE views
         final int count = getChildCount();
         for (int childIdx = 0; childIdx < count; childIdx++) {
-            ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
+            ExpandableView slidingChild = getChildAtIndex(childIdx);
             if (slidingChild.getVisibility() != VISIBLE
                     || (ignoreDecors && slidingChild instanceof StackScrollerDecorView)) {
                 continue;
@@ -1692,6 +1691,10 @@
         return null;
     }
 
+    private ExpandableView getChildAtIndex(int index) {
+        return (ExpandableView) getChildAt(index);
+    }
+
     public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
         getLocationOnScreen(mTempInt2);
         return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
@@ -2277,7 +2280,7 @@
         int childCount = getChildCount();
         int count = 0;
         for (int i = 0; i < childCount; i++) {
-            ExpandableView child = (ExpandableView) getChildAt(i);
+            ExpandableView child = getChildAtIndex(i);
             if (child.getVisibility() != View.GONE && !child.willBeGone() && child != mShelf) {
                 count++;
             }
@@ -2497,7 +2500,7 @@
     private ExpandableView getLastChildWithBackground() {
         int childCount = getChildCount();
         for (int i = childCount - 1; i >= 0; i--) {
-            ExpandableView child = (ExpandableView) getChildAt(i);
+            ExpandableView child = getChildAtIndex(i);
             if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView)
                     && child != mShelf) {
                 return child;
@@ -2510,7 +2513,7 @@
     private ExpandableView getFirstChildWithBackground() {
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
-            ExpandableView child = (ExpandableView) getChildAt(i);
+            ExpandableView child = getChildAtIndex(i);
             if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView)
                     && child != mShelf) {
                 return child;
@@ -2524,7 +2527,7 @@
         ArrayList<ExpandableView> children = new ArrayList<>();
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
-            ExpandableView child = (ExpandableView) getChildAt(i);
+            ExpandableView child = getChildAtIndex(i);
             if (child.getVisibility() != View.GONE
                     && !(child instanceof StackScrollerDecorView)
                     && child != mShelf) {
@@ -2883,7 +2886,7 @@
         }
         int position = 0;
         for (int i = 0; i < getChildCount(); i++) {
-            ExpandableView child = (ExpandableView) getChildAt(i);
+            ExpandableView child = getChildAtIndex(i);
             boolean notGone = child.getVisibility() != View.GONE;
             if (notGone && !child.hasNoContentHeight()) {
                 if (position != 0) {
@@ -2937,7 +2940,7 @@
         }
         mAmbientState.setLastVisibleBackgroundChild(lastChild);
         // TODO: Refactor SectionManager and put the RoundnessManager there.
-        mController.getNoticationRoundessManager().updateRoundedChildren(mSections);
+        mController.getNotificationRoundnessManager().updateRoundedChildren(mSections);
         mAnimateBottomOnLayout = false;
         invalidate();
     }
@@ -3969,7 +3972,7 @@
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void clearUserLockedViews() {
         for (int i = 0; i < getChildCount(); i++) {
-            ExpandableView child = (ExpandableView) getChildAt(i);
+            ExpandableView child = getChildAtIndex(i);
             if (child instanceof ExpandableNotificationRow) {
                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
                 row.setUserLocked(false);
@@ -3982,7 +3985,7 @@
         // lets make sure nothing is transient anymore
         clearTemporaryViewsInGroup(this);
         for (int i = 0; i < getChildCount(); i++) {
-            ExpandableView child = (ExpandableView) getChildAt(i);
+            ExpandableView child = getChildAtIndex(i);
             if (child instanceof ExpandableNotificationRow) {
                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
                 clearTemporaryViewsInGroup(row.getChildrenContainer());
@@ -4020,8 +4023,9 @@
         setOwnScrollY(0);
     }
 
+    @VisibleForTesting
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    private void setIsExpanded(boolean isExpanded) {
+    void setIsExpanded(boolean isExpanded) {
         boolean changed = isExpanded != mIsExpanded;
         mIsExpanded = isExpanded;
         mStackScrollAlgorithm.setIsExpanded(isExpanded);
@@ -4230,7 +4234,7 @@
         if (hideSensitive != mAmbientState.isHideSensitive()) {
             int childCount = getChildCount();
             for (int i = 0; i < childCount; i++) {
-                ExpandableView v = (ExpandableView) getChildAt(i);
+                ExpandableView v = getChildAtIndex(i);
                 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
             }
             mAmbientState.setHideSensitive(hideSensitive);
@@ -4265,7 +4269,7 @@
     private void applyCurrentState() {
         int numChildren = getChildCount();
         for (int i = 0; i < numChildren; i++) {
-            ExpandableView child = (ExpandableView) getChildAt(i);
+            ExpandableView child = getChildAtIndex(i);
             child.applyViewState();
         }
 
@@ -4285,7 +4289,7 @@
 
         // Lefts first sort by Z difference
         for (int i = 0; i < getChildCount(); i++) {
-            ExpandableView child = (ExpandableView) getChildAt(i);
+            ExpandableView child = getChildAtIndex(i);
             if (child.getVisibility() != GONE) {
                 mTmpSortedChildren.add(child);
             }
@@ -4512,7 +4516,7 @@
     public void setClearAllInProgress(boolean clearAllInProgress) {
         mClearAllInProgress = clearAllInProgress;
         mAmbientState.setClearAllInProgress(clearAllInProgress);
-        mController.getNoticationRoundessManager().setClearAllInProgress(clearAllInProgress);
+        mController.getNotificationRoundnessManager().setClearAllInProgress(clearAllInProgress);
     }
 
     boolean getClearAllInProgress() {
@@ -4555,7 +4559,7 @@
         final int count = getChildCount();
         float max = 0;
         for (int childIdx = 0; childIdx < count; childIdx++) {
-            ExpandableView child = (ExpandableView) getChildAt(childIdx);
+            ExpandableView child = getChildAtIndex(childIdx);
             if (child.getVisibility() == GONE) {
                 continue;
             }
@@ -4586,7 +4590,7 @@
     public boolean isBelowLastNotification(float touchX, float touchY) {
         int childCount = getChildCount();
         for (int i = childCount - 1; i >= 0; i--) {
-            ExpandableView child = (ExpandableView) getChildAt(i);
+            ExpandableView child = getChildAtIndex(i);
             if (child.getVisibility() != View.GONE) {
                 float childTop = child.getY();
                 if (childTop > touchY) {
@@ -4842,13 +4846,21 @@
         }
     }
 
+    @VisibleForTesting
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    private void setOwnScrollY(int ownScrollY) {
+    void setOwnScrollY(int ownScrollY) {
         setOwnScrollY(ownScrollY, false /* animateScrollChangeListener */);
     }
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void setOwnScrollY(int ownScrollY, boolean animateStackYChangeListener) {
+        // Avoid Flicking during clear all
+        // when the shade finishes closing, onExpansionStopped will call
+        // resetScrollPosition to setOwnScrollY to 0
+        if (mAmbientState.isClosing()) {
+            return;
+        }
+
         if (ownScrollY != mOwnScrollY) {
             // We still want to call the normal scrolled changed for accessibility reasons
             onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY);
@@ -5044,7 +5056,7 @@
             pw.println();
 
             for (int i = 0; i < childCount; i++) {
-                ExpandableView child = (ExpandableView) getChildAt(i);
+                ExpandableView child = getChildAtIndex(i);
                 child.dump(pw, args);
                 pw.println();
             }
@@ -5333,7 +5345,7 @@
         float wakeUplocation = -1f;
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
-            ExpandableView view = (ExpandableView) getChildAt(i);
+            ExpandableView view = getChildAtIndex(i);
             if (view.getVisibility() == View.GONE) {
                 continue;
             }
@@ -5372,7 +5384,7 @@
     public void setController(
             NotificationStackScrollLayoutController notificationStackScrollLayoutController) {
         mController = notificationStackScrollLayoutController;
-        mController.getNoticationRoundessManager().setAnimatedChildren(mChildrenToAddAnimated);
+        mController.getNotificationRoundnessManager().setAnimatedChildren(mChildrenToAddAnimated);
     }
 
     void addSwipedOutView(View v) {
@@ -5383,31 +5395,22 @@
         if (!(viewSwiped instanceof ExpandableNotificationRow)) {
             return;
         }
-        final int indexOfSwipedView = indexOfChild(viewSwiped);
-        if (indexOfSwipedView < 0) {
-            return;
-        }
         mSectionsManager.updateFirstAndLastViewsForAllSections(
-                mSections, getChildrenWithBackground());
-        View viewBefore = null;
-        if (indexOfSwipedView > 0) {
-            viewBefore = getChildAt(indexOfSwipedView - 1);
-            if (mSectionsManager.beginsSection(viewSwiped, viewBefore)) {
-                viewBefore = null;
-            }
-        }
-        View viewAfter = null;
-        if (indexOfSwipedView < getChildCount()) {
-            viewAfter = getChildAt(indexOfSwipedView + 1);
-            if (mSectionsManager.beginsSection(viewAfter, viewSwiped)) {
-                viewAfter = null;
-            }
-        }
-        mController.getNoticationRoundessManager()
+                mSections,
+                getChildrenWithBackground()
+        );
+
+        RoundableTargets targets = mController.getNotificationTargetsHelper().findRoundableTargets(
+                (ExpandableNotificationRow) viewSwiped,
+                this,
+                mSectionsManager
+        );
+
+        mController.getNotificationRoundnessManager()
                 .setViewsAffectedBySwipe(
-                        (ExpandableView) viewBefore,
-                        (ExpandableView) viewSwiped,
-                        (ExpandableView) viewAfter);
+                        targets.getBefore(),
+                        targets.getSwiped(),
+                        targets.getAfter());
 
         updateFirstAndLastBackgroundViews();
         requestDisallowInterceptTouchEvent(true);
@@ -5418,7 +5421,7 @@
 
     void onSwipeEnd() {
         updateFirstAndLastBackgroundViews();
-        mController.getNoticationRoundessManager()
+        mController.getNotificationRoundnessManager()
                 .setViewsAffectedBySwipe(null, null, null);
         // Round bottom corners for notification right before shelf.
         mShelf.updateAppearance();
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 5c09d61..e1337826 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
@@ -63,7 +63,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.media.KeyguardMediaController;
+import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
@@ -180,6 +180,7 @@
     private int mBarState;
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
     private final FeatureFlags mFeatureFlags;
+    private final NotificationTargetsHelper mNotificationTargetsHelper;
 
     private View mLongPressedView;
 
@@ -642,7 +643,8 @@
             StackStateLogger stackLogger,
             NotificationStackScrollLogger logger,
             NotificationStackSizeCalculator notificationStackSizeCalculator,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            NotificationTargetsHelper notificationTargetsHelper) {
         mStackStateLogger = stackLogger;
         mLogger = logger;
         mAllowLongPress = allowLongPress;
@@ -679,6 +681,7 @@
         mRemoteInputManager = remoteInputManager;
         mShadeController = shadeController;
         mFeatureFlags = featureFlags;
+        mNotificationTargetsHelper = notificationTargetsHelper;
         updateResources();
     }
 
@@ -1380,7 +1383,7 @@
         return mView.calculateGapHeight(previousView, child, count);
     }
 
-    NotificationRoundnessManager getNoticationRoundessManager() {
+    NotificationRoundnessManager getNotificationRoundnessManager() {
         return mNotificationRoundnessManager;
     }
 
@@ -1537,6 +1540,10 @@
         mNotificationActivityStarter = activityStarter;
     }
 
+    public NotificationTargetsHelper getNotificationTargetsHelper() {
+        return mNotificationTargetsHelper;
+    }
+
     /**
      * Enum for UiEvent logged from this class
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
index 5f79c0e..4c52db7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
@@ -1,8 +1,8 @@
 package com.android.systemui.statusbar.notification.stack
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.INFO
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt
new file mode 100644
index 0000000..991a14b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt
@@ -0,0 +1,100 @@
+package com.android.systemui.statusbar.notification.stack
+
+import androidx.core.view.children
+import androidx.core.view.isVisible
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.Roundable
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import javax.inject.Inject
+
+/**
+ * Utility class that helps us find the targets of an animation, often used to find the notification
+ * ([Roundable]) above and below the current one (see [findRoundableTargets]).
+ */
+@SysUISingleton
+class NotificationTargetsHelper
+@Inject
+constructor(
+    featureFlags: FeatureFlags,
+) {
+    private val isNotificationGroupCornerEnabled =
+        featureFlags.isEnabled(Flags.NOTIFICATION_GROUP_CORNER)
+
+    /**
+     * This method looks for views that can be rounded (and implement [Roundable]) during a
+     * notification swipe.
+     * @return The [Roundable] targets above/below the [viewSwiped] (if available). The
+     * [RoundableTargets.before] and [RoundableTargets.after] parameters can be `null` if there is
+     * no above/below notification or the notification is not part of the same section.
+     */
+    fun findRoundableTargets(
+        viewSwiped: ExpandableNotificationRow,
+        stackScrollLayout: NotificationStackScrollLayout,
+        sectionsManager: NotificationSectionsManager,
+    ): RoundableTargets {
+        val viewBefore: Roundable?
+        val viewAfter: Roundable?
+
+        val notificationParent = viewSwiped.notificationParent
+        val childrenContainer = notificationParent?.childrenContainer
+        val visibleStackChildren =
+            stackScrollLayout.children
+                .filterIsInstance<ExpandableView>()
+                .filter { it.isVisible }
+                .toList()
+        if (notificationParent != null && childrenContainer != null) {
+            // We are inside a notification group
+
+            if (!isNotificationGroupCornerEnabled) {
+                return RoundableTargets(null, null, null)
+            }
+
+            val visibleGroupChildren = childrenContainer.attachedChildren.filter { it.isVisible }
+            val indexOfParentSwipedView = visibleGroupChildren.indexOf(viewSwiped)
+
+            viewBefore =
+                visibleGroupChildren.getOrNull(indexOfParentSwipedView - 1)
+                    ?: childrenContainer.notificationHeaderWrapper
+
+            viewAfter =
+                visibleGroupChildren.getOrNull(indexOfParentSwipedView + 1)
+                    ?: visibleStackChildren.indexOf(notificationParent).let {
+                        visibleStackChildren.getOrNull(it + 1)
+                    }
+        } else {
+            // Assumption: we are inside the NotificationStackScrollLayout
+
+            val indexOfSwipedView = visibleStackChildren.indexOf(viewSwiped)
+
+            viewBefore =
+                visibleStackChildren.getOrNull(indexOfSwipedView - 1)?.takeIf {
+                    !sectionsManager.beginsSection(viewSwiped, it)
+                }
+
+            viewAfter =
+                visibleStackChildren.getOrNull(indexOfSwipedView + 1)?.takeIf {
+                    !sectionsManager.beginsSection(it, viewSwiped)
+                }
+        }
+
+        return RoundableTargets(
+            before = viewBefore,
+            swiped = viewSwiped,
+            after = viewAfter,
+        )
+    }
+}
+
+/**
+ * This object contains targets above/below the [swiped] (if available). The [before] and [after]
+ * parameters can be `null` if there is no above/below notification or the notification is not part
+ * of the same section.
+ */
+data class RoundableTargets(
+    val before: Roundable?,
+    val swiped: ExpandableNotificationRow?,
+    val after: Roundable?,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 0502159..eea1d911 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -31,6 +31,7 @@
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -804,7 +805,7 @@
                 row.isLastInSection() ? 1f : (mSmallCornerRadius / mLargeCornerRadius);
         final float roundness = computeCornerRoundnessForPinnedHun(mHostView.getHeight(),
                 ambientState.getStackY(), getMaxAllowedChildHeight(row), originalCornerRadius);
-        row.setBottomRoundness(roundness, /* animate= */ false);
+        row.requestBottomRoundness(roundness, /* animate = */ false, SourceType.OnScroll);
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
index cb4a088..f5de678 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
@@ -1,8 +1,8 @@
 package com.android.systemui.statusbar.notification.stack
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index fa7bfae..169c907 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -262,8 +262,6 @@
     @Override
     void startActivity(Intent intent, boolean dismissShade, Callback callback);
 
-    void setQsExpanded(boolean expanded);
-
     boolean isWakeUpComingFromTouch();
 
     boolean isFalsingThresholdNeeded();
@@ -455,6 +453,9 @@
 
     void collapseShade();
 
+    /** Collapse the shade, but conditional on a flag specific to the trigger of a bugreport. */
+    void collapseShadeForBugreport();
+
     int getWakefulnessState();
 
     boolean isScreenFullyOff();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 3df7273..db8aac95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -91,7 +91,6 @@
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.MathUtils;
-import android.util.Slog;
 import android.view.Display;
 import android.view.IRemoteAnimationRunner;
 import android.view.IWindowManager;
@@ -886,6 +885,11 @@
             mBubblesOptional.get().setExpandListener(mBubbleExpandListener);
         }
 
+        // Do not restart System UI when the bugreport flag changes.
+        mFeatureFlags.addListener(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT, event -> {
+            event.requestNoRestart();
+        });
+
         mStatusBarSignalPolicy.init();
         mKeyguardIndicationController.init();
 
@@ -1137,7 +1141,6 @@
 
         // TODO: Deal with the ugliness that comes from having some of the status bar broken out
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
-        mNotificationLogger.setUpWithContainer(mNotifListContainer);
         mNotificationIconAreaController.setupShelf(mNotificationShelfController);
         mShadeExpansionStateManager.addExpansionListener(mWakeUpCoordinator);
         mUserSwitcherController.init(mNotificationShadeWindowView);
@@ -1422,6 +1425,7 @@
         mStackScrollerController.setNotificationActivityStarter(mNotificationActivityStarter);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
         mNotificationsController.initialize(
+                this,
                 mPresenter,
                 mNotifListContainer,
                 mStackScrollerController.getNotifStackController(),
@@ -1791,18 +1795,6 @@
     }
 
     @Override
-    public void setQsExpanded(boolean expanded) {
-        mNotificationShadeWindowController.setQsExpanded(expanded);
-        mNotificationPanelViewController.setStatusAccessibilityImportance(expanded
-                ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
-        mNotificationPanelViewController.updateSystemUiStateFlags();
-        if (getNavigationBarView() != null) {
-            getNavigationBarView().onStatusBarPanelStateChanged();
-        }
-    }
-
-    @Override
     public boolean isWakeUpComingFromTouch() {
         return mWakeUpComingFromTouch;
     }
@@ -3324,7 +3316,7 @@
     @Override
     public boolean onBackPressed() {
         if (mStatusBarKeyguardViewManager.canHandleBackPressed()) {
-            mStatusBarKeyguardViewManager.onBackPressed(false /* unused */);
+            mStatusBarKeyguardViewManager.onBackPressed();
             return true;
         }
         if (mNotificationPanelViewController.isQsCustomizing()) {
@@ -3581,6 +3573,13 @@
         }
     }
 
+    @Override
+    public void collapseShadeForBugreport() {
+        if (!mFeatureFlags.isEnabled(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT)) {
+            collapseShade();
+        }
+    }
+
     @VisibleForTesting
     final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
         @Override
@@ -3812,8 +3811,7 @@
         if (mDevicePolicyManager.getCameraDisabled(null,
                 mLockscreenUserManager.getCurrentUserId())) {
             return false;
-        } else if (mStatusBarKeyguardViewManager == null
-                || (isKeyguardShowing() && isKeyguardSecure())) {
+        } else if (isKeyguardShowing() && isKeyguardSecure()) {
             // Check if the admin has disabled the camera specifically for the keyguard
             return (mDevicePolicyManager.getKeyguardDisabledFeatures(null,
                     mLockscreenUserManager.getCurrentUserId())
@@ -4221,14 +4219,6 @@
 
     @Override
     public boolean isKeyguardSecure() {
-        if (mStatusBarKeyguardViewManager == null) {
-            // startKeyguard() hasn't been called yet, so we don't know.
-            // Make sure anything that needs to know isKeyguardSecure() checks and re-checks this
-            // value onVisibilityChanged().
-            Slog.w(TAG, "isKeyguardSecure() called before startKeyguard(), returning false",
-                    new Throwable());
-            return false;
-        }
         return mStatusBarKeyguardViewManager.isSecure();
     }
     @Override
@@ -4288,11 +4278,6 @@
             .Callback() {
         @Override
         public void onFinished() {
-            if (mStatusBarKeyguardViewManager == null) {
-                Log.w(TAG, "Tried to notify keyguard visibility when "
-                        + "mStatusBarKeyguardViewManager was null");
-                return;
-            }
             if (mKeyguardStateController.isKeyguardFadingAway()) {
                 mStatusBarKeyguardViewManager.onKeyguardFadedAway();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index d61c51e..9bb4132 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -91,6 +91,11 @@
                         mBouncerPromptReason = mCallback.getBouncerPromptReason();
                     }
                 }
+
+                @Override
+                public void onNonStrongBiometricAllowedChanged(int userId) {
+                    mBouncerPromptReason = mCallback.getBouncerPromptReason();
+                }
             };
     private final Runnable mRemoveViewRunnable = this::removeView;
     private final KeyguardBypassController mKeyguardBypassController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index b987f68..b965ac9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm
@@ -95,14 +96,7 @@
     var bouncerShowing: Boolean = false
     var altBouncerShowing: Boolean = false
     var launchingAffordance: Boolean = false
-    var qSExpanded = false
-        set(value) {
-            val changed = field != value
-            field = value
-            if (changed && !value) {
-                maybePerformPendingUnlock()
-            }
-        }
+    var qsExpanded = false
 
     @Inject
     constructor(
@@ -111,6 +105,7 @@
         statusBarStateController: StatusBarStateController,
         lockscreenUserManager: NotificationLockscreenUserManager,
         keyguardStateController: KeyguardStateController,
+        shadeExpansionStateManager: ShadeExpansionStateManager,
         dumpManager: DumpManager
     ) {
         this.mKeyguardStateController = keyguardStateController
@@ -132,6 +127,14 @@
             }
         })
 
+        shadeExpansionStateManager.addQsExpansionListener { isQsExpanded ->
+            val changed = qsExpanded != isQsExpanded
+            qsExpanded = isQsExpanded
+            if (changed && !isQsExpanded) {
+                maybePerformPendingUnlock()
+            }
+        }
+
         val dismissByDefault = if (context.resources.getBoolean(
                         com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
         tunerService.addTunable(object : TunerService.Tunable {
@@ -160,7 +163,7 @@
     ): Boolean {
         if (biometricSourceType == BiometricSourceType.FACE && bypassEnabled) {
             val can = canBypass()
-            if (!can && (isPulseExpanding || qSExpanded)) {
+            if (!can && (isPulseExpanding || qsExpanded)) {
                 pendingUnlock = PendingUnlock(biometricSourceType, isStrongBiometric)
             }
             return can
@@ -189,7 +192,7 @@
                 altBouncerShowing -> true
                 statusBarStateController.state != StatusBarState.KEYGUARD -> false
                 launchingAffordance -> false
-                isPulseExpanding || qSExpanded -> false
+                isPulseExpanding || qsExpanded -> false
                 else -> true
             }
         }
@@ -214,7 +217,7 @@
         pw.println("  altBouncerShowing: $altBouncerShowing")
         pw.println("  isPulseExpanding: $isPulseExpanding")
         pw.println("  launchingAffordance: $launchingAffordance")
-        pw.println("  qSExpanded: $qSExpanded")
+        pw.println("  qSExpanded: $qsExpanded")
         pw.println("  hasFaceFeature: $hasFaceFeature")
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
index 02b2354..4839fe6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
@@ -19,9 +19,9 @@
 import android.util.DisplayMetrics
 import android.view.View
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.LSShadeTransitionLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
index 00c3e8f..5e2a7c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
@@ -26,6 +26,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.Expandable;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.ActivityStarter;
@@ -67,7 +68,7 @@
                         ActivityLaunchAnimator.Controller.fromView(v, null),
                         true /* showOverlockscreenwhenlocked */, UserHandle.SYSTEM);
             } else {
-                mUserSwitchDialogController.showDialog(v);
+                mUserSwitchDialogController.showDialog(v.getContext(), Expandable.fromView(v));
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 9f93223..8490768 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -249,6 +249,7 @@
     private Callback mCallback;
     private boolean mWallpaperSupportsAmbientMode;
     private boolean mScreenOn;
+    private boolean mTransparentScrimBackground;
 
     // Scrim blanking callbacks
     private Runnable mPendingFrameCallback;
@@ -341,6 +342,8 @@
         mScrimBehind.setDefaultFocusHighlightEnabled(false);
         mNotificationsScrim.setDefaultFocusHighlightEnabled(false);
         mScrimInFront.setDefaultFocusHighlightEnabled(false);
+        mTransparentScrimBackground = notificationsScrim.getResources()
+                .getBoolean(R.bool.notification_scrim_transparent);
         updateScrims();
         mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
     }
@@ -777,13 +780,16 @@
                 float behindFraction = getInterpolatedFraction();
                 behindFraction = (float) Math.pow(behindFraction, 0.8f);
                 if (mClipsQsScrim) {
-                    mBehindAlpha = 1;
-                    mNotificationsAlpha = behindFraction * mDefaultScrimAlpha;
+                    mBehindAlpha = mTransparentScrimBackground ? 0 : 1;
+                    mNotificationsAlpha =
+                            mTransparentScrimBackground ? 0 : behindFraction * mDefaultScrimAlpha;
                 } else {
-                    mBehindAlpha = behindFraction * mDefaultScrimAlpha;
+                    mBehindAlpha =
+                            mTransparentScrimBackground ? 0 : behindFraction * mDefaultScrimAlpha;
                     // Delay fade-in of notification scrim a bit further, to coincide with the
                     // view fade in. Otherwise the empty panel can be quite jarring.
-                    mNotificationsAlpha = MathUtils.constrainedMap(0f, 1f, 0.3f, 0.75f,
+                    mNotificationsAlpha = mTransparentScrimBackground
+                            ? 0 : MathUtils.constrainedMap(0f, 1f, 0.3f, 0.75f,
                             mPanelExpansionFraction);
                 }
                 mBehindTint = mState.getBehindTint();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index ece7ee0..86f6ff8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -372,7 +372,7 @@
             mIconSize = mContext.getResources().getDimensionPixelSize(
                     com.android.internal.R.dimen.status_bar_icon_size);
 
-            if (statusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+            if (statusBarPipelineFlags.useNewMobileIcons()) {
                 // This starts the flow for the new pipeline, and will notify us of changes
                 mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel();
                 MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
@@ -451,7 +451,7 @@
         @VisibleForTesting
         protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
             final BaseStatusBarFrameLayout view;
-            if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+            if (mStatusBarPipelineFlags.useNewWifiIcon()) {
                 view = onCreateModernStatusBarWifiView(slot);
                 // When [ModernStatusBarWifiView] is created, it will automatically apply the
                 // correct view state so we don't need to call applyWifiState.
@@ -474,9 +474,9 @@
                 String slot,
                 MobileIconState state
         ) {
-            if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+            if (mStatusBarPipelineFlags.useNewMobileIcons()) {
                 throw new IllegalStateException("Attempting to add a mobile icon while the new "
-                        + "pipeline is enabled is not supported");
+                        + "icons are enabled is not supported");
             }
 
             // Use the `subId` field as a key to query for the correct context
@@ -497,7 +497,7 @@
                 String slot,
                 int subId
         ) {
-            if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+            if (!mStatusBarPipelineFlags.useNewMobileIcons()) {
                 throw new IllegalStateException("Attempting to add a mobile icon using the new"
                         + "pipeline, but the enabled flag is false.");
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index e106b9e..31e960a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -224,9 +224,9 @@
      */
     @Override
     public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
-        if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+        if (mStatusBarPipelineFlags.useNewMobileIcons()) {
             Log.d(TAG, "ignoring old pipeline callbacks, because the new "
-                    + "pipeline frontend is enabled");
+                    + "icons are enabled");
             return;
         }
         Slot mobileSlot = mStatusBarIconList.getSlot(slot);
@@ -249,9 +249,9 @@
 
     @Override
     public void setNewMobileIconSubIds(List<Integer> subIds) {
-        if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+        if (!mStatusBarPipelineFlags.useNewMobileIcons()) {
             Log.d(TAG, "ignoring new pipeline callback, "
-                    + "since the frontend is disabled");
+                    + "since the new icons are disabled");
             return;
         }
         Slot mobileSlot = mStatusBarIconList.getSlot("mobile");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index b9312c7..ccb5d88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -60,7 +60,6 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.data.BouncerView;
-import com.android.systemui.keyguard.data.BouncerViewDelegate;
 import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
 import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
 import com.android.systemui.navigationbar.NavigationBarView;
@@ -136,7 +135,7 @@
     private KeyguardMessageAreaController<AuthKeyguardMessageArea> mKeyguardMessageAreaController;
     private final BouncerCallbackInteractor mBouncerCallbackInteractor;
     private final BouncerInteractor mBouncerInteractor;
-    private final BouncerViewDelegate mBouncerViewDelegate;
+    private final BouncerView mBouncerView;
     private final Lazy<com.android.systemui.shade.ShadeController> mShadeController;
 
     private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
@@ -203,7 +202,7 @@
         if (DEBUG) {
             Log.d(TAG, "onBackInvokedCallback() called, invoking onBackPressed()");
         }
-        onBackPressed(false /* unused */);
+        onBackPressed();
     };
     private boolean mIsBackCallbackRegistered = false;
 
@@ -225,6 +224,7 @@
     protected CentralSurfaces mCentralSurfaces;
     private NotificationPanelViewController mNotificationPanelViewController;
     private BiometricUnlockController mBiometricUnlockController;
+    private boolean mCentralSurfacesRegistered;
 
     private View mNotificationContainer;
 
@@ -326,7 +326,7 @@
         mKeyguardSecurityModel = keyguardSecurityModel;
         mBouncerCallbackInteractor = bouncerCallbackInteractor;
         mBouncerInteractor = bouncerInteractor;
-        mBouncerViewDelegate = bouncerView.getDelegate();
+        mBouncerView = bouncerView;
         mFoldAodAnimationController = sysUIUnfoldComponent
                 .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
         mIsModernBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_BOUNCER);
@@ -356,6 +356,7 @@
         mNotificationContainer = notificationContainer;
         mKeyguardMessageAreaController = mKeyguardMessageAreaFactory.create(
                 centralSurfaces.getKeyguardMessageArea());
+        mCentralSurfacesRegistered = true;
 
         registerListeners();
     }
@@ -802,7 +803,7 @@
     private void setDozing(boolean dozing) {
         if (mDozing != dozing) {
             mDozing = dozing;
-            if (dozing || mBouncer.needsFullscreenBouncer()
+            if (dozing || needsFullscreenBouncer()
                     || mKeyguardStateController.isOccluded()) {
                 reset(dozing /* hideBouncerWhenShowing */);
             }
@@ -1080,27 +1081,20 @@
      * @return whether a back press can be handled right now.
      */
     public boolean canHandleBackPressed() {
-        return mBouncer.isShowing();
+        return bouncerIsShowing();
     }
 
     /**
      * Notifies this manager that the back button has been pressed.
      */
-    // TODO(b/244635782): This "accept boolean and ignore it, and always return false" was done
-    //                    to make it possible to check this in *and* allow merging to master,
-    //                    where ArcStatusBarKeyguardViewManager inherits this class, and its
-    //                    build will break if we change this interface.
-    //                    So, overall, while this function refactors the behavior of onBackPressed,
-    //                    (it now handles the back press, and no longer returns *whether* it did so)
-    //                    its interface is not changing right now (but will, in a follow-up CL).
-    public boolean onBackPressed(boolean ignored) {
+    public void onBackPressed() {
         if (!canHandleBackPressed()) {
-            return false;
+            return;
         }
 
         mCentralSurfaces.endAffordanceLaunch();
         // The second condition is for SIM card locked bouncer
-        if (bouncerIsScrimmed() && needsFullscreenBouncer()) {
+        if (bouncerIsScrimmed() && !needsFullscreenBouncer()) {
             hideBouncer(false);
             updateStates();
         } else {
@@ -1116,7 +1110,7 @@
                 mNotificationPanelViewController.expandWithoutQs();
             }
         }
-        return false;
+        return;
     }
 
     @Override
@@ -1130,8 +1124,8 @@
     }
 
     public boolean isFullscreenBouncer() {
-        if (mBouncerViewDelegate != null) {
-            return mBouncerViewDelegate.isFullScreenBouncer();
+        if (mBouncerView.getDelegate() != null) {
+            return mBouncerView.getDelegate().isFullScreenBouncer();
         }
         return mBouncer != null && mBouncer.isFullscreenBouncer();
     }
@@ -1175,6 +1169,9 @@
     };
 
     protected void updateStates() {
+        if (!mCentralSurfacesRegistered) {
+            return;
+        }
         boolean showing = mKeyguardStateController.isShowing();
         boolean occluded = mKeyguardStateController.isOccluded();
         boolean bouncerShowing = bouncerIsShowing();
@@ -1287,15 +1284,15 @@
     }
 
     public boolean shouldDismissOnMenuPressed() {
-        if (mBouncerViewDelegate != null) {
-            return mBouncerViewDelegate.shouldDismissOnMenuPressed();
+        if (mBouncerView.getDelegate() != null) {
+            return mBouncerView.getDelegate().shouldDismissOnMenuPressed();
         }
         return mBouncer != null && mBouncer.shouldDismissOnMenuPressed();
     }
 
     public boolean interceptMediaKey(KeyEvent event) {
-        if (mBouncerViewDelegate != null) {
-            return mBouncerViewDelegate.interceptMediaKey(event);
+        if (mBouncerView.getDelegate() != null) {
+            return mBouncerView.getDelegate().interceptMediaKey(event);
         }
         return mBouncer != null && mBouncer.interceptMediaKey(event);
     }
@@ -1304,8 +1301,8 @@
      * @return true if the pre IME back event should be handled
      */
     public boolean dispatchBackKeyEventPreIme() {
-        if (mBouncerViewDelegate != null) {
-            return mBouncerViewDelegate.dispatchBackKeyEventPreIme();
+        if (mBouncerView.getDelegate() != null) {
+            return mBouncerView.getDelegate().dispatchBackKeyEventPreIme();
         }
         return mBouncer != null && mBouncer.dispatchBackKeyEventPreIme();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
index b9a1413..81edff4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
@@ -17,12 +17,12 @@
 package com.android.systemui.statusbar.phone
 
 import android.app.PendingIntent
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
 import com.android.systemui.log.dagger.NotifInteractionLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.ERROR
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogLevel.WARNING
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 64ca2705..a1e0c50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -179,7 +179,6 @@
             mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse);
             notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor);
             mLockscreenUserManager.setUpWithPresenter(this);
-            mMediaManager.setUpWithPresenter(this);
             mGutsManager.setUpWithPresenter(
                     this, mNotifListContainer, mOnSettingsClickListener);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
index 9ae378f..0e6b7f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.phone.fragment
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.CollapsedSbFragmentLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
 import com.android.systemui.statusbar.DisableFlagsLogger
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
index 0d52f46..e498ae4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
@@ -19,6 +19,7 @@
 import android.content.Intent
 import android.os.UserHandle
 import android.view.View
+import com.android.systemui.animation.Expandable
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.ActivityStarter
@@ -75,7 +76,7 @@
                         null /* ActivityLaunchAnimator.Controller */,
                         true /* showOverlockscreenwhenlocked */, UserHandle.SYSTEM)
             } else {
-                userSwitcherDialogController.showDialog(view)
+                userSwitcherDialogController.showDialog(view.context, Expandable.fromView(view))
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
index 9b8b643..06cd12d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -24,29 +24,19 @@
 /** All flagging methods related to the new status bar pipeline (see b/238425913). */
 @SysUISingleton
 class StatusBarPipelineFlags @Inject constructor(private val featureFlags: FeatureFlags) {
-    /**
-     * Returns true if we should run the new pipeline backend.
-     *
-     * The new pipeline backend hooks up to all our external callbacks, logs those callback inputs,
-     * and logs the output state.
-     */
-    fun isNewPipelineBackendEnabled(): Boolean =
-        featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE_BACKEND)
+    /** True if we should display the mobile icons using the new status bar data pipeline. */
+    fun useNewMobileIcons(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS)
+
+    /** True if we should display the wifi icon using the new status bar data pipeline. */
+    fun useNewWifiIcon(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON)
+
+    // TODO(b/238425913): Add flags to only run the mobile backend or wifi backend so we get the
+    //   logging without getting the UI effects.
 
     /**
-     * Returns true if we should run the new pipeline frontend *and* backend.
-     *
-     * The new pipeline frontend will use the outputted state from the new backend and will make the
-     * correct changes to the UI.
-     */
-    fun isNewPipelineFrontendEnabled(): Boolean =
-        isNewPipelineBackendEnabled() &&
-            featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE_FRONTEND)
-
-    /**
-     * Returns true if we should apply some coloring to icons that were rendered with the new
+     * Returns true if we should apply some coloring to the wifi icon that was rendered with the new
      * pipeline to help with debugging.
      */
-    // For now, just always apply the debug coloring if we've enabled frontend rendering.
-    fun useNewPipelineDebugColoring(): Boolean = isNewPipelineFrontendEnabled()
+    // For now, just always apply the debug coloring if we've enabled the new icon.
+    fun useWifiDebugColoring(): Boolean = useNewWifiIcon()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt
new file mode 100644
index 0000000..7aa5ee1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.statusbar.pipeline.airplane.data.repository
+
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings.Global
+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.qs.SettingObserver
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
+import com.android.systemui.util.settings.GlobalSettings
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Provides data related to airplane mode.
+ *
+ * IMPORTANT: This is currently *not* used to render any airplane mode information anywhere. It is
+ * only used to help [com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel]
+ * determine what parts of the wifi icon view should be shown.
+ *
+ * TODO(b/238425913): Consider migrating the status bar airplane mode icon to use this repo.
+ */
+interface AirplaneModeRepository {
+    /** Observable for whether the device is currently in airplane mode. */
+    val isAirplaneMode: StateFlow<Boolean>
+}
+
+@SysUISingleton
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+class AirplaneModeRepositoryImpl
+@Inject
+constructor(
+    @Background private val bgHandler: Handler,
+    private val globalSettings: GlobalSettings,
+    logger: ConnectivityPipelineLogger,
+    @Application scope: CoroutineScope,
+) : AirplaneModeRepository {
+    // TODO(b/254848912): Replace this with a generic SettingObserver coroutine once we have it.
+    override val isAirplaneMode: StateFlow<Boolean> =
+        conflatedCallbackFlow {
+                val observer =
+                    object :
+                        SettingObserver(
+                            globalSettings,
+                            bgHandler,
+                            Global.AIRPLANE_MODE_ON,
+                            UserHandle.USER_ALL
+                        ) {
+                        override fun handleValueChanged(value: Int, observedChange: Boolean) {
+                            trySend(value == 1)
+                        }
+                    }
+
+                observer.isListening = true
+                trySend(observer.value == 1)
+                awaitClose { observer.isListening = false }
+            }
+            .distinctUntilChanged()
+            .logInputChange(logger, "isAirplaneMode")
+            .stateIn(
+                scope,
+                started = SharingStarted.WhileSubscribed(),
+                // When the observer starts listening, the flow will emit the current value so the
+                // initialValue here is irrelevant.
+                initialValue = false,
+            )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt
new file mode 100644
index 0000000..3e9b2c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.statusbar.pipeline.airplane.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/**
+ * The business logic layer for airplane mode.
+ *
+ * IMPORTANT: This is currently *not* used to render any airplane mode information anywhere. See
+ * [AirplaneModeRepository] for more details.
+ */
+@SysUISingleton
+class AirplaneModeInteractor
+@Inject
+constructor(
+    airplaneModeRepository: AirplaneModeRepository,
+    connectivityRepository: ConnectivityRepository,
+) {
+    /** True if the device is currently in airplane mode. */
+    val isAirplaneMode: Flow<Boolean> = airplaneModeRepository.isAirplaneMode
+
+    /** True if we're configured to force-hide the airplane mode icon and false otherwise. */
+    val isForceHidden: Flow<Boolean> =
+        connectivityRepository.forceHiddenSlots.map { it.contains(ConnectivitySlot.AIRPLANE) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt
new file mode 100644
index 0000000..fe30c01
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.statusbar.pipeline.airplane.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+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.distinctUntilChanged
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Models the UI state for the status bar airplane mode icon.
+ *
+ * IMPORTANT: This is currently *not* used to render any airplane mode information anywhere. See
+ * [com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository] for
+ * more details.
+ */
+@SysUISingleton
+class AirplaneModeViewModel
+@Inject
+constructor(
+    interactor: AirplaneModeInteractor,
+    logger: ConnectivityPipelineLogger,
+    @Application private val scope: CoroutineScope,
+) {
+    /** True if the airplane mode icon is currently visible in the status bar. */
+    val isAirplaneModeIconVisible: StateFlow<Boolean> =
+        combine(interactor.isAirplaneMode, interactor.isForceHidden) {
+                isAirplaneMode,
+                isAirplaneIconForceHidden ->
+                isAirplaneMode && !isAirplaneIconForceHidden
+            }
+            .distinctUntilChanged()
+            .logOutputChange(logger, "isAirplaneModeIconVisible")
+            .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 06d5542..2aaa085 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.pipeline.dagger
 
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepositoryImpl
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
@@ -30,6 +32,9 @@
 @Module
 abstract class StatusBarPipelineModule {
     @Binds
+    abstract fun airplaneModeRepository(impl: AirplaneModeRepositoryImpl): AirplaneModeRepository
+
+    @Binds
     abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
index dbb1aa5..d3cf32f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -18,10 +18,10 @@
 
 import android.net.Network
 import android.net.NetworkCapabilities
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.StatusBarConnectivityLog
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.toString
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index 681cf72..93448c1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -39,7 +39,6 @@
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
 import java.util.concurrent.Executor
@@ -64,6 +63,9 @@
     /** Observable for the current wifi enabled status. */
     val isWifiEnabled: StateFlow<Boolean>
 
+    /** Observable for the current wifi default status. */
+    val isWifiDefault: StateFlow<Boolean>
+
     /** Observable for the current wifi network. */
     val wifiNetwork: StateFlow<WifiNetworkModel>
 
@@ -103,7 +105,7 @@
             merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
                 .mapLatest { wifiManager.isWifiEnabled }
                 .distinctUntilChanged()
-                .logOutputChange(logger, "enabled")
+                .logInputChange(logger, "enabled")
                 .stateIn(
                     scope = scope,
                     started = SharingStarted.WhileSubscribed(),
@@ -111,6 +113,39 @@
                 )
         }
 
+    override val isWifiDefault: StateFlow<Boolean> = conflatedCallbackFlow {
+        // Note: This callback doesn't do any logging because we already log every network change
+        // in the [wifiNetwork] callback.
+        val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
+            override fun onCapabilitiesChanged(
+                network: Network,
+                networkCapabilities: NetworkCapabilities
+            ) {
+                // This method will always be called immediately after the network becomes the
+                // default, in addition to any time the capabilities change while the network is
+                // the default.
+                // If this network contains valid wifi info, then wifi is the default network.
+                val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
+                trySend(wifiInfo != null)
+            }
+
+            override fun onLost(network: Network) {
+                // The system no longer has a default network, so wifi is definitely not default.
+                trySend(false)
+            }
+        }
+
+        connectivityManager.registerDefaultNetworkCallback(callback)
+        awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+    }
+        .distinctUntilChanged()
+        .logInputChange(logger, "isWifiDefault")
+        .stateIn(
+            scope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = false
+        )
+
     override val wifiNetwork: StateFlow<WifiNetworkModel> = conflatedCallbackFlow {
         var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
index 04b17ed..3a3e611 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -59,6 +59,9 @@
     /** Our current enabled status. */
     val isEnabled: Flow<Boolean> = wifiRepository.isWifiEnabled
 
+    /** Our current default status. */
+    val isDefault: Flow<Boolean> = wifiRepository.isWifiDefault
+
     /** Our current wifi network. See [WifiNetworkModel]. */
     val wifiNetwork: Flow<WifiNetworkModel> = wifiRepository.wifiNetwork
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 273be63..25537b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -91,6 +91,7 @@
         val activityInView = view.requireViewById<ImageView>(R.id.wifi_in)
         val activityOutView = view.requireViewById<ImageView>(R.id.wifi_out)
         val activityContainerView = view.requireViewById<View>(R.id.inout_container)
+        val airplaneSpacer = view.requireViewById<View>(R.id.wifi_airplane_spacer)
 
         view.isVisible = true
         iconView.isVisible = true
@@ -142,6 +143,12 @@
                         activityContainerView.isVisible = visible
                     }
                 }
+
+                launch {
+                    viewModel.isAirplaneSpacerVisible.distinctUntilChanged().collect { visible ->
+                        airplaneSpacer.isVisible = visible
+                    }
+                }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
index 40f948f..95ab251 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
@@ -32,6 +32,7 @@
     isActivityInViewVisible: Flow<Boolean>,
     isActivityOutViewVisible: Flow<Boolean>,
     isActivityContainerVisible: Flow<Boolean>,
+    isAirplaneSpacerVisible: Flow<Boolean>,
 ) :
     LocationBasedWifiViewModel(
         statusBarPipelineFlags,
@@ -40,4 +41,5 @@
         isActivityInViewVisible,
         isActivityOutViewVisible,
         isActivityContainerVisible,
+        isAirplaneSpacerVisible,
     )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
index 9642ac4..86535d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
@@ -29,6 +29,7 @@
     isActivityInViewVisible: Flow<Boolean>,
     isActivityOutViewVisible: Flow<Boolean>,
     isActivityContainerVisible: Flow<Boolean>,
+    isAirplaneSpacerVisible: Flow<Boolean>,
 ) :
     LocationBasedWifiViewModel(
         statusBarPipelineFlags,
@@ -37,4 +38,5 @@
         isActivityInViewVisible,
         isActivityOutViewVisible,
         isActivityContainerVisible,
+        isAirplaneSpacerVisible,
     )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
index e23f8c7..7cbdf5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
@@ -44,11 +44,14 @@
 
     /** True if the activity container view should be visible. */
     val isActivityContainerVisible: Flow<Boolean>,
+
+    /** True if the airplane spacer view should be visible. */
+    val isAirplaneSpacerVisible: Flow<Boolean>,
 ) {
     /** The color that should be used to tint the icon. */
     val tint: Flow<Int> =
         flowOf(
-            if (statusBarPipelineFlags.useNewPipelineDebugColoring()) {
+            if (statusBarPipelineFlags.useWifiDebugColoring()) {
                 debugTint
             } else {
                 DEFAULT_TINT
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
index 0ddf90e..fd54c5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
@@ -29,6 +29,7 @@
     isActivityInViewVisible: Flow<Boolean>,
     isActivityOutViewVisible: Flow<Boolean>,
     isActivityContainerVisible: Flow<Boolean>,
+    isAirplaneSpacerVisible: Flow<Boolean>,
 ) :
     LocationBasedWifiViewModel(
         statusBarPipelineFlags,
@@ -37,4 +38,5 @@
         isActivityInViewVisible,
         isActivityOutViewVisible,
         isActivityContainerVisible,
+        isAirplaneSpacerVisible,
     )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index ebbd77b..89b96b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
@@ -66,6 +67,7 @@
 class WifiViewModel
 @Inject
 constructor(
+    airplaneModeViewModel: AirplaneModeViewModel,
     connectivityConstants: ConnectivityConstants,
     private val context: Context,
     logger: ConnectivityPipelineLogger,
@@ -124,9 +126,10 @@
     private val wifiIcon: StateFlow<Icon.Resource?> =
         combine(
             interactor.isEnabled,
+            interactor.isDefault,
             interactor.isForceHidden,
             interactor.wifiNetwork,
-        ) { isEnabled, isForceHidden, wifiNetwork ->
+        ) { isEnabled, isDefault, isForceHidden, wifiNetwork ->
             if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) {
                 return@combine null
             }
@@ -135,6 +138,7 @@
             val icon = Icon.Resource(iconResId, wifiNetwork.contentDescription())
 
             return@combine when {
+                isDefault -> icon
                 wifiConstants.alwaysShowIconIfEnabled -> icon
                 !connectivityConstants.hasDataCapabilities -> icon
                 wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon
@@ -175,6 +179,12 @@
                 }
              .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
 
+    // TODO(b/238425913): It isn't ideal for the wifi icon to need to know about whether the
+    //  airplane icon is visible. Instead, we should have a parent StatusBarSystemIconsViewModel
+    //  that appropriately knows about both icons and sets the padding appropriately.
+    private val isAirplaneSpacerVisible: Flow<Boolean> =
+        airplaneModeViewModel.isAirplaneModeIconVisible
+
     /** A view model for the status bar on the home screen. */
     val home: HomeWifiViewModel =
         HomeWifiViewModel(
@@ -183,6 +193,7 @@
             isActivityInViewVisible,
             isActivityOutViewVisible,
             isActivityContainerVisible,
+            isAirplaneSpacerVisible,
         )
 
     /** A view model for the status bar on keyguard. */
@@ -193,6 +204,7 @@
             isActivityInViewVisible,
             isActivityOutViewVisible,
             isActivityContainerVisible,
+            isAirplaneSpacerVisible,
         )
 
     /** A view model for the status bar in quick settings. */
@@ -203,6 +215,7 @@
             isActivityInViewVisible,
             isActivityOutViewVisible,
             isActivityContainerVisible,
+            isAirplaneSpacerVisible,
         )
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
index 2f0ebf7..28a9b97 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
@@ -43,11 +43,7 @@
     }
 
     override fun getCount(): Int {
-        return if (controller.isKeyguardShowing) {
-            users.count { !it.isRestricted }
-        } else {
-            users.size
-        }
+        return users.size
     }
 
     override fun getItem(position: Int): UserRecord {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index d7c81af..df1e80b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -16,10 +16,10 @@
 
 package com.android.systemui.statusbar.policy
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.VERBOSE
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogLevel.VERBOSE
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index dc73d1f..f63d652 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -36,6 +36,7 @@
 import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
 import com.android.settingslib.drawable.CircleFramedDrawable;
 import com.android.systemui.R;
+import com.android.systemui.animation.Expandable;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -190,7 +191,8 @@
             mUiEventLogger.log(
                     LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP);
 
-            mUserSwitchDialogController.showDialog(mUserAvatarViewWithBackground);
+            mUserSwitchDialogController.showDialog(mUserAvatarViewWithBackground.getContext(),
+                    Expandable.fromView(mUserAvatarViewWithBackground));
         });
 
         mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index da6d455..dd400b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -47,6 +47,7 @@
 import android.view.View;
 import android.view.ViewAnimationUtils;
 import android.view.ViewGroup;
+import android.view.ViewRootImpl;
 import android.view.WindowInsets;
 import android.view.WindowInsetsAnimation;
 import android.view.WindowInsetsController;
@@ -61,6 +62,8 @@
 import android.widget.LinearLayout;
 import android.widget.ProgressBar;
 import android.widget.TextView;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -88,6 +91,7 @@
  */
 public class RemoteInputView extends LinearLayout implements View.OnClickListener {
 
+    private static final boolean DEBUG = false;
     private static final String TAG = "RemoteInput";
 
     // A marker object that let's us easily find views of this class.
@@ -124,6 +128,7 @@
     // TODO(b/193539698): remove this; views shouldn't have access to their controller, and places
     //  that need the controller shouldn't have access to the view
     private RemoteInputViewController mViewController;
+    private ViewRootImpl mTestableViewRootImpl;
 
     /**
      * Enum for logged notification remote input UiEvents.
@@ -430,10 +435,20 @@
         }
     }
 
+    @VisibleForTesting
+    protected void setViewRootImpl(ViewRootImpl viewRoot) {
+        mTestableViewRootImpl = viewRoot;
+    }
+
+    @VisibleForTesting
+    protected void setEditTextReferenceToSelf() {
+        mEditText.mRemoteInputView = this;
+    }
+
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        mEditText.mRemoteInputView = this;
+        setEditTextReferenceToSelf();
         mEditText.setOnEditorActionListener(mEditorActionHandler);
         mEditText.addTextChangedListener(mTextWatcher);
         if (mEntry.getRow().isChangingPosition()) {
@@ -457,7 +472,50 @@
     }
 
     @Override
+    public ViewRootImpl getViewRootImpl() {
+        if (mTestableViewRootImpl != null) {
+            return mTestableViewRootImpl;
+        }
+        return super.getViewRootImpl();
+    }
+
+    private void registerBackCallback() {
+        ViewRootImpl viewRoot = getViewRootImpl();
+        if (viewRoot == null) {
+            if (DEBUG) {
+                Log.d(TAG, "ViewRoot was null, NOT registering Predictive Back callback");
+            }
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "registering Predictive Back callback");
+        }
+        viewRoot.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+                OnBackInvokedDispatcher.PRIORITY_OVERLAY, mEditText.mOnBackInvokedCallback);
+    }
+
+    private void unregisterBackCallback() {
+        ViewRootImpl viewRoot = getViewRootImpl();
+        if (viewRoot == null) {
+            if (DEBUG) {
+                Log.d(TAG, "ViewRoot was null, NOT unregistering Predictive Back callback");
+            }
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "unregistering Predictive Back callback");
+        }
+        viewRoot.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(
+                mEditText.mOnBackInvokedCallback);
+    }
+
+    @Override
     public void onVisibilityAggregated(boolean isVisible) {
+        if (isVisible) {
+            registerBackCallback();
+        } else {
+            unregisterBackCallback();
+        }
         super.onVisibilityAggregated(isVisible);
         mEditText.setEnabled(isVisible && !mSending);
     }
@@ -822,10 +880,21 @@
             return super.onKeyDown(keyCode, event);
         }
 
+        private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
+            if (DEBUG) {
+                Log.d(TAG, "Predictive Back Callback dispatched");
+            }
+            respondToKeycodeBack();
+        };
+
+        private void respondToKeycodeBack() {
+            defocusIfNeeded(true /* animate */);
+        }
+
         @Override
         public boolean onKeyUp(int keyCode, KeyEvent event) {
             if (keyCode == KeyEvent.KEYCODE_BACK) {
-                defocusIfNeeded(true /* animate */);
+                respondToKeycodeBack();
                 return true;
             }
             return super.onKeyUp(keyCode, event);
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index 5cbdf7c..f0a50de 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.temporarydisplay
 
 import android.annotation.LayoutRes
-import android.annotation.SuppressLint
 import android.content.Context
 import android.graphics.PixelFormat
 import android.graphics.Rect
@@ -67,11 +66,10 @@
      * Window layout params that will be used as a starting point for the [windowLayoutParams] of
      * all subclasses.
      */
-    @SuppressLint("WrongConstant") // We're allowed to use TYPE_VOLUME_OVERLAY
     internal val commonWindowLayoutParams = WindowManager.LayoutParams().apply {
         width = WindowManager.LayoutParams.WRAP_CONTENT
         height = WindowManager.LayoutParams.WRAP_CONTENT
-        type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
+        type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR
         flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
             WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
         title = windowTitle
@@ -131,7 +129,7 @@
        )
         cancelViewTimeout?.run()
         cancelViewTimeout = mainExecutor.executeDelayed(
-            { removeView(TemporaryDisplayRemovalReason.REASON_TIMEOUT) },
+            { removeView(REMOVAL_REASON_TIMEOUT) },
             timeout.toLong()
         )
     }
@@ -175,9 +173,6 @@
      */
     fun removeView(removalReason: String) {
         val currentDisplayInfo = displayInfo ?: return
-        if (shouldIgnoreViewRemoval(currentDisplayInfo.info, removalReason)) {
-            return
-        }
 
         val currentView = currentDisplayInfo.view
         animateViewOut(currentView) { windowManager.removeView(currentView) }
@@ -193,13 +188,6 @@
     }
 
     /**
-     * Returns true if a view removal request should be ignored and false otherwise.
-     *
-     * Allows subclasses to keep the view visible for longer in certain circumstances.
-     */
-    open fun shouldIgnoreViewRemoval(info: T, removalReason: String): Boolean = false
-
-    /**
      * A method implemented by subclasses to update [currentView] based on [newInfo].
      */
     abstract fun updateView(newInfo: T, currentView: ViewGroup)
@@ -236,10 +224,7 @@
     )
 }
 
-object TemporaryDisplayRemovalReason {
-    const val REASON_TIMEOUT = "TIMEOUT"
-    const val REASON_SCREEN_TAP = "SCREEN_TAP"
-}
+private const val REMOVAL_REASON_TIMEOUT = "TIMEOUT"
 
 private data class IconInfo(
     val iconName: String,
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index 606a11a..a7185cb 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -16,8 +16,8 @@
 
 package com.android.systemui.temporarydisplay
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
 
 /** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */
 open class TemporaryViewLogger(
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
new file mode 100644
index 0000000..cd7bd2d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.temporarydisplay.chipbar
+
+import android.content.Context
+import android.graphics.Rect
+import android.os.PowerManager
+import android.view.Gravity
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
+import android.widget.TextView
+import com.android.internal.widget.CachingIconView
+import com.android.systemui.Gefingerpoken
+import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.animation.ViewHierarchyAnimator
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Text.Companion.loadText
+import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.common.ui.binder.TextViewBinder
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.media.taptotransfer.common.MediaTttUtils
+import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.view.ViewUtil
+import javax.inject.Inject
+
+/**
+ * A coordinator for showing/hiding the chipbar.
+ *
+ * The chipbar is a UI element that displays on top of all content. It appears at the top of the
+ * screen and consists of an icon, one line of text, and an optional end icon or action. It will
+ * auto-dismiss after some amount of seconds. The user is *not* able to manually dismiss the
+ * chipbar.
+ *
+ * It should be only be used for critical and temporary information that the user *must* be aware
+ * of. In general, prefer using heads-up notifications, since they are dismissable and will remain
+ * in the list of notifications until the user dismisses them.
+ *
+ * Only one chipbar may be shown at a time.
+ * TODO(b/245610654): Should we just display whichever chipbar was most recently requested, or do we
+ *   need to maintain a priority ordering?
+ *
+ * TODO(b/245610654): Remove all media-related items from this class so it's just for generic
+ *   chipbars.
+ */
+@SysUISingleton
+open class ChipbarCoordinator @Inject constructor(
+        context: Context,
+        @MediaTttSenderLogger logger: MediaTttLogger,
+        windowManager: WindowManager,
+        @Main mainExecutor: DelayableExecutor,
+        accessibilityManager: AccessibilityManager,
+        configurationController: ConfigurationController,
+        powerManager: PowerManager,
+        private val falsingManager: FalsingManager,
+        private val falsingCollector: FalsingCollector,
+        private val viewUtil: ViewUtil,
+        private val vibratorHelper: VibratorHelper,
+) : TemporaryViewDisplayController<ChipbarInfo, MediaTttLogger>(
+        context,
+        logger,
+        windowManager,
+        mainExecutor,
+        accessibilityManager,
+        configurationController,
+        powerManager,
+        R.layout.chipbar,
+        MediaTttUtils.WINDOW_TITLE,
+        MediaTttUtils.WAKE_REASON,
+) {
+
+    private lateinit var parent: ChipbarRootView
+
+    override val windowLayoutParams = commonWindowLayoutParams.apply {
+        gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
+    }
+
+    override fun start() {}
+
+    override fun updateView(
+        newInfo: ChipbarInfo,
+        currentView: ViewGroup
+    ) {
+        // TODO(b/245610654): Adding logging here.
+
+        // Detect falsing touches on the chip.
+        parent = currentView.requireViewById(R.id.chipbar_root_view)
+        parent.touchHandler = object : Gefingerpoken {
+            override fun onTouchEvent(ev: MotionEvent?): Boolean {
+                falsingCollector.onTouchEvent(ev)
+                return false
+            }
+        }
+
+        // ---- Start icon ----
+        val iconView = currentView.requireViewById<CachingIconView>(R.id.start_icon)
+        IconViewBinder.bind(newInfo.startIcon, iconView)
+
+        // ---- Text ----
+        val textView = currentView.requireViewById<TextView>(R.id.text)
+        TextViewBinder.bind(textView, newInfo.text)
+        // Updates text view bounds to make sure it perfectly fits the new text
+        // (If the new text is smaller than the previous text) see b/253228632.
+        textView.requestLayout()
+
+        // ---- End item ----
+        // Loading
+        currentView.requireViewById<View>(R.id.loading).visibility =
+            (newInfo.endItem == ChipbarEndItem.Loading).visibleIfTrue()
+
+        // Error
+        currentView.requireViewById<View>(R.id.error).visibility =
+            (newInfo.endItem == ChipbarEndItem.Error).visibleIfTrue()
+
+        // Button
+        val buttonView = currentView.requireViewById<TextView>(R.id.end_button)
+        if (newInfo.endItem is ChipbarEndItem.Button) {
+            TextViewBinder.bind(buttonView, newInfo.endItem.text)
+
+            val onClickListener = View.OnClickListener { clickedView ->
+                if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener
+                newInfo.endItem.onClickListener.onClick(clickedView)
+            }
+
+            buttonView.setOnClickListener(onClickListener)
+            buttonView.visibility = View.VISIBLE
+        } else {
+            buttonView.visibility = View.GONE
+        }
+
+        // ---- Overall accessibility ----
+        currentView.requireViewById<ViewGroup>(
+                R.id.chipbar_inner
+        ).contentDescription =
+            "${newInfo.startIcon.contentDescription.loadContentDescription(context)} " +
+                "${newInfo.text.loadText(context)}"
+
+        // ---- Haptics ----
+        newInfo.vibrationEffect?.let {
+            vibratorHelper.vibrate(it)
+        }
+    }
+
+    override fun animateViewIn(view: ViewGroup) {
+        val chipInnerView = view.requireViewById<ViewGroup>(R.id.chipbar_inner)
+        ViewHierarchyAnimator.animateAddition(
+            chipInnerView,
+            ViewHierarchyAnimator.Hotspot.TOP,
+            Interpolators.EMPHASIZED_DECELERATE,
+            duration = ANIMATION_DURATION,
+            includeMargins = true,
+            includeFadeIn = true,
+            // We can only request focus once the animation finishes.
+            onAnimationEnd = { chipInnerView.requestAccessibilityFocus() },
+        )
+    }
+
+    override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+        ViewHierarchyAnimator.animateRemoval(
+            view.requireViewById<ViewGroup>(R.id.chipbar_inner),
+            ViewHierarchyAnimator.Hotspot.TOP,
+            Interpolators.EMPHASIZED_ACCELERATE,
+            ANIMATION_DURATION,
+            includeMargins = true,
+            onAnimationEnd,
+        )
+    }
+
+    override fun getTouchableRegion(view: View, outRect: Rect) {
+        viewUtil.setRectToViewWindowLocation(view, outRect)
+    }
+
+    private fun Boolean.visibleIfTrue(): Int {
+        return if (this) {
+            View.VISIBLE
+        } else {
+            View.GONE
+        }
+    }
+}
+
+const val SENDER_TAG = "MediaTapToTransferSender"
+private const val ANIMATION_DURATION = 500L
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
new file mode 100644
index 0000000..57fde87
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.temporarydisplay.chipbar
+
+import android.os.VibrationEffect
+import android.view.View
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.temporarydisplay.TemporaryViewInfo
+
+/**
+ * A container for all the state needed to display a chipbar via [ChipbarCoordinator].
+ *
+ * @property startIcon the icon to display at the start of the chipbar (on the left in LTR locales;
+ * on the right in RTL locales).
+ * @property text the text to display.
+ * @property endItem an optional end item to display at the end of the chipbar (on the right in LTR
+ * locales; on the left in RTL locales).
+ * @property vibrationEffect an optional vibration effect when the chipbar is displayed
+ */
+data class ChipbarInfo(
+    val startIcon: Icon,
+    val text: Text,
+    val endItem: ChipbarEndItem?,
+    val vibrationEffect: VibrationEffect? = null,
+) : TemporaryViewInfo
+
+/** The possible items to display at the end of the chipbar. */
+sealed class ChipbarEndItem {
+    /** A loading icon should be displayed. */
+    object Loading : ChipbarEndItem()
+
+    /** An error icon should be displayed. */
+    object Error : ChipbarEndItem()
+
+    /**
+     * A button with the provided [text] and [onClickListener] functionality should be displayed.
+     */
+    data class Button(val text: Text, val onClickListener: View.OnClickListener) : ChipbarEndItem()
+
+    // TODO(b/245610654): Add support for a generic icon.
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarRootView.kt
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt
rename to packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarRootView.kt
index 3373159..edec420 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarRootView.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.taptotransfer.sender
+package com.android.systemui.temporarydisplay.chipbar
 
 import android.content.Context
 import android.util.AttributeSet
@@ -22,8 +22,8 @@
 import android.widget.FrameLayout
 import com.android.systemui.Gefingerpoken
 
-/** A simple subclass that allows for observing touch events on chip. */
-class MediaTttChipRootView(
+/** A simple subclass that allows for observing touch events on chipbar. */
+class ChipbarRootView(
         context: Context,
         attrs: AttributeSet?
 ) : FrameLayout(context, attrs) {
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 3d56f23..3ecb15b 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -79,6 +79,7 @@
 import org.json.JSONObject;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
@@ -114,6 +115,7 @@
     private final SecureSettings mSecureSettings;
     private final Executor mMainExecutor;
     private final Handler mBgHandler;
+    private final boolean mIsMonochromaticEnabled;
     private final Context mContext;
     private final boolean mIsMonetEnabled;
     private final UserTracker mUserTracker;
@@ -363,6 +365,7 @@
             UserTracker userTracker, DumpManager dumpManager, FeatureFlags featureFlags,
             @Main Resources resources, WakefulnessLifecycle wakefulnessLifecycle) {
         mContext = context;
+        mIsMonochromaticEnabled = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEMES);
         mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
         mDeviceProvisionedController = deviceProvisionedController;
         mBroadcastDispatcher = broadcastDispatcher;
@@ -665,8 +668,13 @@
         // Allow-list of Style objects that can be created from a setting string, i.e. can be
         // used as a system-wide theme.
         // - Content intentionally excluded, intended for media player, not system-wide
-        List<Style> validStyles = Arrays.asList(Style.EXPRESSIVE, Style.SPRITZ, Style.TONAL_SPOT,
-                Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT);
+        List<Style> validStyles = new ArrayList<>(Arrays.asList(Style.EXPRESSIVE, Style.SPRITZ,
+                Style.TONAL_SPOT, Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT));
+
+        if (mIsMonochromaticEnabled) {
+            validStyles.add(Style.MONOCHROMATIC);
+        }
+
         Style style = mThemeStyle;
         final String overlayPackageJson = mSecureSettings.getStringForUser(
                 Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
index 51541bd..fda5114 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
@@ -16,11 +16,11 @@
 
 package com.android.systemui.toast
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogMessage
 import com.android.systemui.log.dagger.ToastLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogMessage
 import javax.inject.Inject
 
 private const val TAG = "ToastLog"
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
index 7f1195b..7da2d47 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
@@ -17,7 +17,8 @@
 package com.android.systemui.user
 
 import android.os.Bundle
-import android.view.View
+import android.view.WindowInsets.Type
+import android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
 import androidx.activity.ComponentActivity
 import androidx.lifecycle.ViewModelProvider
 import com.android.systemui.R
@@ -38,10 +39,10 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.user_switcher_fullscreen)
-        window.decorView.systemUiVisibility =
-            (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
-                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
-                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
+        window.decorView.getWindowInsetsController().apply {
+            setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE)
+            hide(Type.systemBars())
+        }
         val viewModel =
             ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java]
         UserSwitcherViewBinder.bind(
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
index ee785b6..088cd93 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
@@ -36,9 +36,7 @@
     private var adapter: ListAdapter? = null
 
     init {
-        setBackgroundDrawable(
-            res.getDrawable(R.drawable.bouncer_user_switcher_popup_bg, context.getTheme())
-        )
+        setBackgroundDrawable(null)
         setModal(false)
         setOverlapAnchor(true)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 919e699..b16dc54 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -220,7 +220,12 @@
             val result = withContext(backgroundDispatcher) { manager.aliveUsers }
 
             if (result != null) {
-                _userInfos.value = result.sortedBy { it.creationTime }
+                _userInfos.value =
+                    result
+                        // Users should be sorted by ascending creation time.
+                        .sortedBy { it.creationTime }
+                        // The guest user is always last, regardless of creation time.
+                        .sortedBy { it.isGuest }
             }
         }
     }
@@ -321,6 +326,7 @@
         return when {
             isAddUser -> false
             isAddSupervisedUser -> false
+            isManageUsers -> false
             isGuest -> info != null
             else -> true
         }
@@ -346,6 +352,7 @@
             isAddUser -> UserActionModel.ADD_USER
             isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER
             isGuest -> UserActionModel.ENTER_GUEST_MODE
+            isManageUsers -> UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
             else -> error("Don't know how to convert to UserActionModel: $this")
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index ba5a82a..dda78aa 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -236,18 +236,7 @@
                     }
                     .flatMapLatest { isActionable ->
                         if (isActionable) {
-                            repository.actions.map { actions ->
-                                actions +
-                                    if (actions.isNotEmpty()) {
-                                        // If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT
-                                        // because that's a user switcher specific action that is
-                                        // not known to the our data source or other features.
-                                        listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-                                    } else {
-                                        // If no actions, don't add the navigate action.
-                                        emptyList()
-                                    }
-                            }
+                            repository.actions
                         } else {
                             // If not actionable it means that we're not allowed to show actions
                             // when
@@ -440,6 +429,7 @@
                         isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
                         isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
                         onExitGuestUser = this::exitGuestUser,
+                        dialogShower = dialogShower,
                     )
                 )
                 return
@@ -454,6 +444,7 @@
                         isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
                         isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
                         onExitGuestUser = this::exitGuestUser,
+                        dialogShower = dialogShower,
                     )
                 )
                 return
@@ -488,6 +479,7 @@
                             userHandle = currentUser.userHandle,
                             isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
                             showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral,
+                            dialogShower = dialogShower,
                         )
                     )
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
index 08d7c5a..177356e 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
@@ -18,14 +18,18 @@
 package com.android.systemui.user.domain.model
 
 import android.os.UserHandle
+import com.android.systemui.qs.user.UserSwitchDialogController
 
 /** Encapsulates a request to show a dialog. */
-sealed class ShowDialogRequestModel {
+sealed class ShowDialogRequestModel(
+    open val dialogShower: UserSwitchDialogController.DialogShower? = null,
+) {
     data class ShowAddUserDialog(
         val userHandle: UserHandle,
         val isKeyguardShowing: Boolean,
         val showEphemeralMessage: Boolean,
-    ) : ShowDialogRequestModel()
+        override val dialogShower: UserSwitchDialogController.DialogShower?,
+    ) : ShowDialogRequestModel(dialogShower)
 
     data class ShowUserCreationDialog(
         val isGuest: Boolean,
@@ -37,5 +41,6 @@
         val isGuestEphemeral: Boolean,
         val isKeyguardShowing: Boolean,
         val onExitGuestUser: (guestId: Int, targetId: Int, forceRemoveGuest: Boolean) -> Unit,
-    ) : ShowDialogRequestModel()
+        override val dialogShower: UserSwitchDialogController.DialogShower?,
+    ) : ShowDialogRequestModel(dialogShower)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
index 938417f..968af59 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
@@ -18,12 +18,15 @@
 package com.android.systemui.user.ui.binder
 
 import android.content.Context
+import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
 import android.widget.BaseAdapter
 import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.LinearLayout.SHOW_DIVIDER_MIDDLE
 import android.widget.TextView
 import androidx.constraintlayout.helper.widget.Flow as FlowWidget
 import androidx.core.view.isVisible
@@ -36,6 +39,7 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.user.UserSwitcherPopupMenu
 import com.android.systemui.user.UserSwitcherRootView
+import com.android.systemui.user.shared.model.UserActionModel
 import com.android.systemui.user.ui.viewmodel.UserActionViewModel
 import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
 import com.android.systemui.util.children
@@ -168,15 +172,10 @@
         onDismissed: () -> Unit,
     ): UserSwitcherPopupMenu {
         return UserSwitcherPopupMenu(context).apply {
+            this.setDropDownGravity(Gravity.END)
             this.anchorView = anchorView
             setAdapter(adapter)
             setOnDismissListener { onDismissed() }
-            setOnItemClickListener { _, _, position, _ ->
-                val itemPositionExcludingHeader = position - 1
-                adapter.getItem(itemPositionExcludingHeader).onClicked()
-                dismiss()
-            }
-
             show()
         }
     }
@@ -186,38 +185,67 @@
         private val layoutInflater: LayoutInflater,
     ) : BaseAdapter() {
 
-        private val items = mutableListOf<UserActionViewModel>()
+        private var sections = listOf<List<UserActionViewModel>>()
 
         override fun getCount(): Int {
-            return items.size
+            return sections.size
         }
 
-        override fun getItem(position: Int): UserActionViewModel {
-            return items[position]
+        override fun getItem(position: Int): List<UserActionViewModel> {
+            return sections[position]
         }
 
         override fun getItemId(position: Int): Long {
-            return getItem(position).viewKey
+            return position.toLong()
         }
 
         override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
-            val view =
-                convertView
-                    ?: layoutInflater.inflate(
+            val section = getItem(position)
+            val context = parent.context
+            val sectionView =
+                convertView as? LinearLayout
+                    ?: LinearLayout(context, null).apply {
+                        this.orientation = LinearLayout.VERTICAL
+                        this.background =
+                            parent.resources.getDrawable(
+                                R.drawable.bouncer_user_switcher_popup_bg,
+                                context.theme
+                            )
+                        this.showDividers = SHOW_DIVIDER_MIDDLE
+                        this.dividerDrawable =
+                            context.getDrawable(
+                                R.drawable.fullscreen_userswitcher_menu_item_divider
+                            )
+                    }
+            sectionView.removeAllViewsInLayout()
+
+            for (viewModel in section) {
+                val view =
+                    layoutInflater.inflate(
                         R.layout.user_switcher_fullscreen_popup_item,
-                        parent,
-                        false
+                        /* parent= */ null
                     )
-            val viewModel = getItem(position)
-            view.requireViewById<ImageView>(R.id.icon).setImageResource(viewModel.iconResourceId)
-            view.requireViewById<TextView>(R.id.text).text =
-                view.resources.getString(viewModel.textResourceId)
-            return view
+                view
+                    .requireViewById<ImageView>(R.id.icon)
+                    .setImageResource(viewModel.iconResourceId)
+                view.requireViewById<TextView>(R.id.text).text =
+                    view.resources.getString(viewModel.textResourceId)
+                view.setOnClickListener { viewModel.onClicked() }
+                sectionView.addView(view)
+            }
+            return sectionView
         }
 
         fun setItems(items: List<UserActionViewModel>) {
-            this.items.clear()
-            this.items.addAll(items)
+            val primarySection =
+                items.filter {
+                    it.viewKey != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong()
+                }
+            val secondarySection =
+                items.filter {
+                    it.viewKey == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong()
+                }
+            this.sections = listOf(primarySection, secondarySection)
             notifyDataSetChanged()
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index 91c5921..e921720 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -19,8 +19,10 @@
 
 import android.app.Dialog
 import android.content.Context
+import com.android.internal.jank.InteractionJankMonitor
 import com.android.settingslib.users.UserCreatingDialog
 import com.android.systemui.CoreStartable
+import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.dagger.SysUISingleton
@@ -30,6 +32,7 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.collect
@@ -41,19 +44,19 @@
 class UserSwitcherDialogCoordinator
 @Inject
 constructor(
-    @Application private val context: Context,
-    @Application private val applicationScope: CoroutineScope,
-    private val falsingManager: FalsingManager,
-    private val broadcastSender: BroadcastSender,
-    private val dialogLaunchAnimator: DialogLaunchAnimator,
-    private val interactor: UserInteractor,
-    private val featureFlags: FeatureFlags,
+    @Application private val context: Lazy<Context>,
+    @Application private val applicationScope: Lazy<CoroutineScope>,
+    private val falsingManager: Lazy<FalsingManager>,
+    private val broadcastSender: Lazy<BroadcastSender>,
+    private val dialogLaunchAnimator: Lazy<DialogLaunchAnimator>,
+    private val interactor: Lazy<UserInteractor>,
+    private val featureFlags: Lazy<FeatureFlags>,
 ) : CoreStartable {
 
     private var currentDialog: Dialog? = null
 
     override fun start() {
-        if (featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)) {
+        if (featureFlags.get().isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)) {
             return
         }
 
@@ -62,61 +65,87 @@
     }
 
     private fun startHandlingDialogShowRequests() {
-        applicationScope.launch {
-            interactor.dialogShowRequests.filterNotNull().collect { request ->
+        applicationScope.get().launch {
+            interactor.get().dialogShowRequests.filterNotNull().collect { request ->
                 currentDialog?.let {
                     if (it.isShowing) {
                         it.cancel()
                     }
                 }
 
-                currentDialog =
+                val (dialog, dialogCuj) =
                     when (request) {
                         is ShowDialogRequestModel.ShowAddUserDialog ->
-                            AddUserDialog(
-                                context = context,
-                                userHandle = request.userHandle,
-                                isKeyguardShowing = request.isKeyguardShowing,
-                                showEphemeralMessage = request.showEphemeralMessage,
-                                falsingManager = falsingManager,
-                                broadcastSender = broadcastSender,
-                                dialogLaunchAnimator = dialogLaunchAnimator,
+                            Pair(
+                                AddUserDialog(
+                                    context = context.get(),
+                                    userHandle = request.userHandle,
+                                    isKeyguardShowing = request.isKeyguardShowing,
+                                    showEphemeralMessage = request.showEphemeralMessage,
+                                    falsingManager = falsingManager.get(),
+                                    broadcastSender = broadcastSender.get(),
+                                    dialogLaunchAnimator = dialogLaunchAnimator.get(),
+                                ),
+                                DialogCuj(
+                                    InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
+                                    INTERACTION_JANK_ADD_NEW_USER_TAG,
+                                ),
                             )
                         is ShowDialogRequestModel.ShowUserCreationDialog ->
-                            UserCreatingDialog(
-                                context,
-                                request.isGuest,
+                            Pair(
+                                UserCreatingDialog(
+                                    context.get(),
+                                    request.isGuest,
+                                ),
+                                null,
                             )
                         is ShowDialogRequestModel.ShowExitGuestDialog ->
-                            ExitGuestDialog(
-                                context = context,
-                                guestUserId = request.guestUserId,
-                                isGuestEphemeral = request.isGuestEphemeral,
-                                targetUserId = request.targetUserId,
-                                isKeyguardShowing = request.isKeyguardShowing,
-                                falsingManager = falsingManager,
-                                dialogLaunchAnimator = dialogLaunchAnimator,
-                                onExitGuestUserListener = request.onExitGuestUser,
+                            Pair(
+                                ExitGuestDialog(
+                                    context = context.get(),
+                                    guestUserId = request.guestUserId,
+                                    isGuestEphemeral = request.isGuestEphemeral,
+                                    targetUserId = request.targetUserId,
+                                    isKeyguardShowing = request.isKeyguardShowing,
+                                    falsingManager = falsingManager.get(),
+                                    dialogLaunchAnimator = dialogLaunchAnimator.get(),
+                                    onExitGuestUserListener = request.onExitGuestUser,
+                                ),
+                                DialogCuj(
+                                    InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
+                                    INTERACTION_JANK_EXIT_GUEST_MODE_TAG,
+                                ),
                             )
                     }
+                currentDialog = dialog
 
-                currentDialog?.show()
-                interactor.onDialogShown()
+                if (request.dialogShower != null && dialogCuj != null) {
+                    request.dialogShower?.showDialog(dialog, dialogCuj)
+                } else {
+                    dialog.show()
+                }
+
+                interactor.get().onDialogShown()
             }
         }
     }
 
     private fun startHandlingDialogDismissRequests() {
-        applicationScope.launch {
-            interactor.dialogDismissRequests.filterNotNull().collect {
+        applicationScope.get().launch {
+            interactor.get().dialogDismissRequests.filterNotNull().collect {
                 currentDialog?.let {
                     if (it.isShowing) {
                         it.cancel()
                     }
                 }
 
-                interactor.onDialogDismissed()
+                interactor.get().onDialogDismissed()
             }
         }
     }
+
+    companion object {
+        private const val INTERACTION_JANK_ADD_NEW_USER_TAG = "add_new_user"
+        private const val INTERACTION_JANK_EXIT_GUEST_MODE_TAG = "exit_guest_mode"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index 219dae2..d857e85 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -62,17 +62,7 @@
     val isMenuVisible: Flow<Boolean> = _isMenuVisible
     /** The user action menu. */
     val menu: Flow<List<UserActionViewModel>> =
-        userInteractor.actions.map { actions ->
-            if (isNewImpl && actions.isNotEmpty()) {
-                    // If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT because that's a user
-                    // switcher specific action that is not known to the our data source or other
-                    // features.
-                    actions + listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-                } else {
-                    actions
-                }
-                .map { action -> toViewModel(action) }
-        }
+        userInteractor.actions.map { actions -> actions.map { action -> toViewModel(action) } }
 
     /** Whether the button to open the user action menu is visible. */
     val isOpenMenuButtonVisible: Flow<Boolean> = menu.map { it.isNotEmpty() }
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
index ecb365f..2c317dd 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
@@ -172,10 +172,14 @@
         return Boolean.TRUE.equals(mIsConditionMet);
     }
 
-    private boolean shouldLog() {
+    protected final boolean shouldLog() {
         return Log.isLoggable(mTag, Log.DEBUG);
     }
 
+    protected final String getTag() {
+        return mTag;
+    }
+
     /**
      * Callback that receives updates about whether the condition has been fulfilled.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
index 4824f67..cb430ba 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
@@ -117,6 +117,7 @@
         final SubscriptionState state = new SubscriptionState(subscription);
 
         mExecutor.execute(() -> {
+            if (shouldLog()) Log.d(mTag, "adding subscription");
             mSubscriptions.put(token, state);
 
             // Add and associate conditions.
@@ -143,7 +144,7 @@
      */
     public void removeSubscription(@NotNull Subscription.Token token) {
         mExecutor.execute(() -> {
-            if (shouldLog()) Log.d(mTag, "removing callback");
+            if (shouldLog()) Log.d(mTag, "removing subscription");
             if (!mSubscriptions.containsKey(token)) {
                 Log.e(mTag, "subscription not present:" + token);
                 return;
diff --git a/packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto b/packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto
new file mode 100644
index 0000000..b7166d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+package com.android.systemui.util;
+
+option java_multiple_files = true;
+
+message ComponentNameProto {
+  string package_name = 1;
+  string class_name = 2;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index fbc6a58..309f168 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -22,6 +22,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
@@ -55,6 +56,8 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.systemui.tracing.nano.SystemUiTraceProto;
+import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.floating.FloatingTasks;
 import com.android.wm.shell.nano.WmShellTraceProto;
 import com.android.wm.shell.onehanded.OneHanded;
@@ -111,6 +114,7 @@
     private final Optional<SplitScreen> mSplitScreenOptional;
     private final Optional<OneHanded> mOneHandedOptional;
     private final Optional<FloatingTasks> mFloatingTasksOptional;
+    private final Optional<DesktopMode> mDesktopModeOptional;
 
     private final CommandQueue mCommandQueue;
     private final ConfigurationController mConfigurationController;
@@ -173,6 +177,7 @@
             Optional<SplitScreen> splitScreenOptional,
             Optional<OneHanded> oneHandedOptional,
             Optional<FloatingTasks> floatingTasksOptional,
+            Optional<DesktopMode> desktopMode,
             CommandQueue commandQueue,
             ConfigurationController configurationController,
             KeyguardStateController keyguardStateController,
@@ -194,6 +199,7 @@
         mPipOptional = pipOptional;
         mSplitScreenOptional = splitScreenOptional;
         mOneHandedOptional = oneHandedOptional;
+        mDesktopModeOptional = desktopMode;
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mProtoTracer = protoTracer;
         mUserTracker = userTracker;
@@ -219,6 +225,7 @@
         mPipOptional.ifPresent(this::initPip);
         mSplitScreenOptional.ifPresent(this::initSplitScreen);
         mOneHandedOptional.ifPresent(this::initOneHanded);
+        mDesktopModeOptional.ifPresent(this::initDesktopMode);
     }
 
     @VisibleForTesting
@@ -326,6 +333,16 @@
         });
     }
 
+    void initDesktopMode(DesktopMode desktopMode) {
+        desktopMode.addListener(new DesktopModeTaskRepository.VisibleTasksListener() {
+            @Override
+            public void onVisibilityChanged(boolean hasFreeformTasks) {
+                mSysUiState.setFlag(SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE, hasFreeformTasks)
+                        .commitUpdate(DEFAULT_DISPLAY);
+            }
+        }, mSysUiMainExecutor);
+    }
+
     @Override
     public void writeToProto(SystemUiTraceProto proto) {
         if (proto.wmShell == null) {
diff --git a/packages/SystemUI/tests/Android.bp b/packages/SystemUI/tests/Android.bp
new file mode 100644
index 0000000..3c418ed
--- /dev/null
+++ b/packages/SystemUI/tests/Android.bp
@@ -0,0 +1,50 @@
+//
+// 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 {
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_test {
+    name: "SystemUITests",
+
+    dxflags: ["--multi-dex"],
+    platform_apis: true,
+    test_suites: ["device-tests"],
+    static_libs: ["SystemUI-tests"],
+    compile_multilib: "both",
+
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libmultiplejvmtiagentsinterferenceagent",
+        "libstaticjvmtiagent",
+    ],
+    libs: [
+        "android.test.runner",
+        "telephony-common",
+        "android.test.base",
+    ],
+    aaptflags: [
+        "--extra-packages com.android.systemui",
+    ],
+
+    // sign this with platform cert, so this test is allowed to inject key events into
+    // UI it doesn't own. This is necessary to allow screenshots to be taken
+    certificate: "platform",
+
+    additional_manifests: ["AndroidManifest.xml"],
+    manifest: "AndroidManifest-base.xml",
+}
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
deleted file mode 100644
index ff5165d..0000000
--- a/packages/SystemUI/tests/Android.mk
+++ /dev/null
@@ -1,93 +0,0 @@
-# Copyright (C) 2011 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.
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_USE_AAPT2 := true
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_JACK_FLAGS := --multi-dex native
-LOCAL_DX_FLAGS := --multi-dex
-
-LOCAL_PACKAGE_NAME := SystemUITests
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../NOTICE
-LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_COMPATIBILITY_SUITE := device-tests
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
-    SystemUI-tests
-
-LOCAL_MULTILIB := both
-
-LOCAL_JNI_SHARED_LIBRARIES := \
-    libdexmakerjvmtiagent \
-    libmultiplejvmtiagentsinterferenceagent \
-    libstaticjvmtiagent
-
-LOCAL_JAVA_LIBRARIES := \
-    android.test.runner \
-    telephony-common \
-    android.test.base \
-
-LOCAL_AAPT_FLAGS := --extra-packages com.android.systemui
-
-# sign this with platform cert, so this test is allowed to inject key events into
-# UI it doesn't own. This is necessary to allow screenshots to be taken
-LOCAL_CERTIFICATE := platform
-
-LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest.xml
-LOCAL_MANIFEST_FILE := AndroidManifest-base.xml
-
-# Provide jack a list of classes to exclude from code coverage.
-# This is needed because the SystemUITests compile SystemUI source directly, rather than using
-# LOCAL_INSTRUMENTATION_FOR := SystemUI.
-#
-# We want to exclude the test classes from code coverage measurements, but they share the same
-# package as the rest of SystemUI so they can't be easily filtered by package name.
-#
-# Generate a comma separated list of patterns based on the test source files under src/
-# SystemUI classes are in ../src/ so they won't be excluded.
-# Example:
-#   Input files: src/com/android/systemui/Test.java src/com/android/systemui/AnotherTest.java
-#   Generated exclude list: com.android.systemui.Test*,com.android.systemui.AnotherTest*
-
-# Filter all src files under src/ to just java files
-local_java_files := $(filter %.java,$(call all-java-files-under, src))
-# Transform java file names into full class names.
-# This only works if the class name matches the file name and the directory structure
-# matches the package.
-local_classes := $(subst /,.,$(patsubst src/%.java,%,$(local_java_files)))
-local_comma := ,
-local_empty :=
-local_space := $(local_empty) $(local_empty)
-# Convert class name list to jacoco exclude list
-# This appends a * to all classes and replace the space separators with commas.
-jacoco_exclude := $(subst $(space),$(comma),$(patsubst %,%*,$(local_classes)))
-
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.systemui.*,com.android.keyguard.*
-LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude)
-
-ifeq ($(EXCLUDE_SYSTEMUI_TESTS),)
-    include $(BUILD_PACKAGE)
-endif
-
-# Reset variables
-local_java_files :=
-local_classes :=
-local_comma :=
-local_space :=
-jacoco_exclude :=
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index ba28045..1b404a8 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -88,6 +88,11 @@
                   android:excludeFromRecents="true"
                   />
 
+        <activity android:name=".settings.brightness.BrightnessDialogTest$TestDialog"
+            android:exported="false"
+            android:excludeFromRecents="true"
+            />
+
         <activity android:name="com.android.systemui.screenshot.ScrollViewActivity"
                   android:exported="false" />
 
diff --git a/packages/SystemUI/tests/res/layout/custom_view_dark.xml b/packages/SystemUI/tests/res/layout/custom_view_dark.xml
index 9e460a5..112d73d2 100644
--- a/packages/SystemUI/tests/res/layout/custom_view_dark.xml
+++ b/packages/SystemUI/tests/res/layout/custom_view_dark.xml
@@ -14,6 +14,7 @@
     limitations under the License.
 -->
 <ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/custom_view_dark_image"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="#ff000000"
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
new file mode 100644
index 0000000..7b9b39f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 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 android.content.Context
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.util.AttributeSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class BouncerKeyguardMessageAreaTest : SysuiTestCase() {
+    class FakeBouncerKeyguardMessageArea(context: Context, attrs: AttributeSet?) :
+        BouncerKeyguardMessageArea(context, attrs) {
+        override val SHOW_DURATION_MILLIS = 0L
+        override val HIDE_DURATION_MILLIS = 0L
+    }
+    lateinit var underTest: BouncerKeyguardMessageArea
+
+    @Before
+    fun setup() {
+        underTest = FakeBouncerKeyguardMessageArea(context, null)
+    }
+
+    @Test
+    fun testSetSameMessage() {
+        val underTestSpy = spy(underTest)
+        underTestSpy.setMessage("abc")
+        underTestSpy.setMessage("abc")
+        verify(underTestSpy, times(1)).text = "abc"
+    }
+
+    @Test
+    fun testSetDifferentMessage() {
+        underTest.setMessage("abc")
+        underTest.setMessage("def")
+        assertThat(underTest.text).isEqualTo("def")
+    }
+
+    @Test
+    fun testSetNullMessage() {
+        underTest.setMessage(null)
+        assertThat(underTest.text).isEqualTo("")
+    }
+
+    @Test
+    fun testSetNullClearsPreviousMessage() {
+        underTest.setMessage("something not null")
+        assertThat(underTest.text).isEqualTo("something not null")
+
+        underTest.setMessage(null)
+        assertThat(underTest.text).isEqualTo("")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 8a2c354..1c3656d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -17,17 +17,22 @@
 
 import android.content.BroadcastReceiver
 import android.testing.AndroidTestingRunner
+import android.view.View
 import android.widget.TextView
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.plugins.ClockAnimations
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.plugins.ClockEvents
 import com.android.systemui.plugins.ClockFaceController
 import com.android.systemui.plugins.ClockFaceEvents
-import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.mockito.any
@@ -37,6 +42,9 @@
 import com.android.systemui.util.mockito.mock
 import java.util.TimeZone
 import java.util.concurrent.Executor
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Rule
@@ -57,7 +65,7 @@
 class ClockEventControllerTest : SysuiTestCase() {
 
     @JvmField @Rule val mockito = MockitoJUnit.rule()
-    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var keyguardInteractor: KeyguardInteractor
     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
     @Mock private lateinit var batteryController: BatteryController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@@ -72,8 +80,11 @@
     @Mock private lateinit var largeClockController: ClockFaceController
     @Mock private lateinit var smallClockEvents: ClockFaceEvents
     @Mock private lateinit var largeClockEvents: ClockFaceEvents
-
-    private lateinit var clockEventController: ClockEventController
+    @Mock private lateinit var parentView: View
+    @Mock private lateinit var transitionRepository: KeyguardTransitionRepository
+    private lateinit var repository: FakeKeyguardRepository
+    @Mock private lateinit var logBuffer: LogBuffer
+    private lateinit var underTest: ClockEventController
 
     @Before
     fun setUp() {
@@ -86,8 +97,11 @@
         whenever(clock.events).thenReturn(events)
         whenever(clock.animations).thenReturn(animations)
 
-        clockEventController = ClockEventController(
-            statusBarStateController,
+        repository = FakeKeyguardRepository()
+
+        underTest = ClockEventController(
+            KeyguardInteractor(repository = repository),
+            KeyguardTransitionInteractor(repository = transitionRepository),
             broadcastDispatcher,
             batteryController,
             keyguardUpdateMonitor,
@@ -96,33 +110,36 @@
             context,
             mainExecutor,
             bgExecutor,
+            logBuffer,
             featureFlags
         )
+        underTest.clock = clock
+
+        runBlocking(IMMEDIATE) {
+            underTest.registerListeners(parentView)
+
+            repository.setDozing(true)
+            repository.setDozeAmount(1f)
+        }
     }
 
     @Test
     fun clockSet_validateInitialization() {
-        clockEventController.clock = clock
-
         verify(clock).initialize(any(), anyFloat(), anyFloat())
     }
 
     @Test
     fun clockUnset_validateState() {
-        clockEventController.clock = clock
-        clockEventController.clock = null
+        underTest.clock = null
 
-        assertEquals(clockEventController.clock, null)
+        assertEquals(underTest.clock, null)
     }
 
     @Test
-    fun themeChanged_verifyClockPaletteUpdated() {
-        clockEventController.clock = clock
+    fun themeChanged_verifyClockPaletteUpdated() = runBlocking(IMMEDIATE) {
         verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
         verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
 
-        clockEventController.registerListeners()
-
         val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
         verify(configurationController).addCallback(capture(captor))
         captor.value.onThemeChanged()
@@ -131,13 +148,10 @@
     }
 
     @Test
-    fun fontChanged_verifyFontSizeUpdated() {
-        clockEventController.clock = clock
+    fun fontChanged_verifyFontSizeUpdated() = runBlocking(IMMEDIATE) {
         verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
         verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
 
-        clockEventController.registerListeners()
-
         val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
         verify(configurationController).addCallback(capture(captor))
         captor.value.onDensityOrFontScaleChanged()
@@ -146,10 +160,7 @@
     }
 
     @Test
-    fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() {
-        clockEventController.clock = clock
-        clockEventController.registerListeners()
-
+    fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() = runBlocking(IMMEDIATE) {
         val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
         verify(batteryController).addCallback(capture(batteryCaptor))
         val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
@@ -161,26 +172,21 @@
     }
 
     @Test
-    fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() {
-        clockEventController.clock = clock
-        clockEventController.registerListeners()
+    fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() =
+        runBlocking(IMMEDIATE) {
+            val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+            verify(batteryController).addCallback(capture(batteryCaptor))
+            val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+            verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
+            keyguardCaptor.value.onKeyguardVisibilityChanged(true)
+            batteryCaptor.value.onBatteryLevelChanged(10, false, true)
+            batteryCaptor.value.onBatteryLevelChanged(10, false, true)
 
-        val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
-        verify(batteryController).addCallback(capture(batteryCaptor))
-        val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
-        verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
-        keyguardCaptor.value.onKeyguardVisibilityChanged(true)
-        batteryCaptor.value.onBatteryLevelChanged(10, false, true)
-        batteryCaptor.value.onBatteryLevelChanged(10, false, true)
-
-        verify(animations, times(1)).charge()
-    }
+            verify(animations, times(1)).charge()
+        }
 
     @Test
-    fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() {
-        clockEventController.clock = clock
-        clockEventController.registerListeners()
-
+    fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() = runBlocking(IMMEDIATE) {
         val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
         verify(batteryController).addCallback(capture(batteryCaptor))
         val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
@@ -192,25 +198,20 @@
     }
 
     @Test
-    fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() {
-        clockEventController.clock = clock
-        clockEventController.registerListeners()
+    fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() =
+        runBlocking(IMMEDIATE) {
+            val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+            verify(batteryController).addCallback(capture(batteryCaptor))
+            val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+            verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
+            keyguardCaptor.value.onKeyguardVisibilityChanged(true)
+            batteryCaptor.value.onBatteryLevelChanged(10, false, false)
 
-        val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
-        verify(batteryController).addCallback(capture(batteryCaptor))
-        val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
-        verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
-        keyguardCaptor.value.onKeyguardVisibilityChanged(true)
-        batteryCaptor.value.onBatteryLevelChanged(10, false, false)
-
-        verify(animations, never()).charge()
-    }
+            verify(animations, never()).charge()
+        }
 
     @Test
-    fun localeCallback_verifyClockNotified() {
-        clockEventController.clock = clock
-        clockEventController.registerListeners()
-
+    fun localeCallback_verifyClockNotified() = runBlocking(IMMEDIATE) {
         val captor = argumentCaptor<BroadcastReceiver>()
         verify(broadcastDispatcher).registerReceiver(
             capture(captor), any(), eq(null), eq(null), anyInt(), eq(null)
@@ -221,10 +222,7 @@
     }
 
     @Test
-    fun keyguardCallback_visibilityChanged_clockDozeCalled() {
-        clockEventController.clock = clock
-        clockEventController.registerListeners()
-
+    fun keyguardCallback_visibilityChanged_clockDozeCalled() = runBlocking(IMMEDIATE) {
         val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
         verify(keyguardUpdateMonitor).registerCallback(capture(captor))
 
@@ -236,10 +234,7 @@
     }
 
     @Test
-    fun keyguardCallback_timeFormat_clockNotified() {
-        clockEventController.clock = clock
-        clockEventController.registerListeners()
-
+    fun keyguardCallback_timeFormat_clockNotified() = runBlocking(IMMEDIATE) {
         val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
         verify(keyguardUpdateMonitor).registerCallback(capture(captor))
         captor.value.onTimeFormatChanged("12h")
@@ -248,11 +243,8 @@
     }
 
     @Test
-    fun keyguardCallback_timezoneChanged_clockNotified() {
+    fun keyguardCallback_timezoneChanged_clockNotified() = runBlocking(IMMEDIATE) {
         val mockTimeZone = mock<TimeZone>()
-        clockEventController.clock = clock
-        clockEventController.registerListeners()
-
         val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
         verify(keyguardUpdateMonitor).registerCallback(capture(captor))
         captor.value.onTimeZoneChanged(mockTimeZone)
@@ -261,10 +253,7 @@
     }
 
     @Test
-    fun keyguardCallback_userSwitched_clockNotified() {
-        clockEventController.clock = clock
-        clockEventController.registerListeners()
-
+    fun keyguardCallback_userSwitched_clockNotified() = runBlocking(IMMEDIATE) {
         val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
         verify(keyguardUpdateMonitor).registerCallback(capture(captor))
         captor.value.onUserSwitchComplete(10)
@@ -273,25 +262,27 @@
     }
 
     @Test
-    fun keyguardCallback_verifyKeyguardChanged() {
-        clockEventController.clock = clock
-        clockEventController.registerListeners()
+    fun keyguardCallback_verifyKeyguardChanged() = runBlocking(IMMEDIATE) {
+        val job = underTest.listenForDozeAmount(this)
+        repository.setDozeAmount(0.4f)
 
-        val captor = argumentCaptor<StatusBarStateController.StateListener>()
-        verify(statusBarStateController).addCallback(capture(captor))
-        captor.value.onDozeAmountChanged(0.4f, 0.6f)
+        yield()
 
         verify(animations).doze(0.4f)
+
+        job.cancel()
     }
 
     @Test
-    fun unregisterListeners_validate() {
-        clockEventController.clock = clock
-        clockEventController.unregisterListeners()
+    fun unregisterListeners_validate() = runBlocking(IMMEDIATE) {
+        underTest.unregisterListeners()
         verify(broadcastDispatcher).unregisterReceiver(any())
         verify(configurationController).removeCallback(any())
         verify(batteryController).removeCallback(any())
         verify(keyguardUpdateMonitor).removeCallback(any())
-        verify(statusBarStateController).removeCallback(any())
+    }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 400caa3..627d738 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -29,6 +29,7 @@
 
 import android.content.res.Resources;
 import android.database.ContentObserver;
+import android.graphics.Rect;
 import android.net.Uri;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -45,6 +46,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.plugins.ClockAnimations;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.clocks.AnimatableClockView;
@@ -262,9 +264,22 @@
         verify(mView).switchToClock(KeyguardClockSwitch.SMALL, /* animate */ true);
     }
 
+    @Test
+    public void testGetClockAnimationsForwardsToClock() {
+        ClockController mockClockController = mock(ClockController.class);
+        ClockAnimations mockClockAnimations = mock(ClockAnimations.class);
+        when(mClockEventController.getClock()).thenReturn(mockClockController);
+        when(mockClockController.getAnimations()).thenReturn(mockClockAnimations);
+
+        Rect r1 = new Rect(1, 2, 3, 4);
+        Rect r2 = new Rect(5, 6, 7, 8);
+        mController.getClockAnimations().onPositionUpdated(r1, r2, 0.2f);
+        verify(mockClockAnimations).onPositionUpdated(r1, r2, 0.2f);
+    }
+
     private void verifyAttachment(VerificationMode times) {
         verify(mClockRegistry, times).registerClockChangeListener(
                 any(ClockRegistry.ClockChangeListener.class));
-        verify(mClockEventController, times).registerListeners();
+        verify(mClockEventController, times).registerListeners(mView);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
index 69524e5..5d2b0ca 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -17,13 +17,11 @@
 package com.android.keyguard;
 
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 
-import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -92,19 +90,4 @@
         mMessageAreaController.setIsVisible(true);
         verify(mKeyguardMessageArea).setIsVisible(true);
     }
-
-    @Test
-    public void testSetMessageIfEmpty_empty() {
-        mMessageAreaController.setMessage("");
-        mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin);
-        verify(mKeyguardMessageArea).setMessage(R.string.keyguard_enter_your_pin);
-    }
-
-    @Test
-    public void testSetMessageIfEmpty_notEmpty() {
-        mMessageAreaController.setMessage("abc");
-        mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin);
-        verify(mKeyguardMessageArea, never()).setMessage(getContext()
-                .getResources().getText(R.string.keyguard_enter_your_pin));
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index b89dbd9..b369098 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -114,9 +114,8 @@
     }
 
     @Test
-    fun onResume_testSetInitialText() {
-        keyguardPasswordViewController.onResume(KeyguardSecurityView.SCREEN_ON)
-        verify(mKeyguardMessageAreaController)
-            .setMessageIfEmpty(R.string.keyguard_enter_your_password)
+    fun startAppearAnimation() {
+        keyguardPasswordViewController.startAppearAnimation()
+        verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 3262a77..9eff704 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -100,16 +100,16 @@
     }
 
     @Test
-    fun onPause_clearsTextField() {
+    fun onPause_resetsText() {
         mKeyguardPatternViewController.init()
         mKeyguardPatternViewController.onPause()
-        verify(mKeyguardMessageAreaController).setMessage("")
+        verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
     }
 
+
     @Test
-    fun onResume_setInitialText() {
-        mKeyguardPatternViewController.onResume(KeyguardSecurityView.SCREEN_ON)
-        verify(mKeyguardMessageAreaController)
-            .setMessageIfEmpty(R.string.keyguard_enter_your_pattern)
+    fun startAppearAnimation() {
+        mKeyguardPatternViewController.startAppearAnimation()
+        verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index 97d556b..ce1101f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -113,11 +113,4 @@
         mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON);
         verify(mPasswordEntry).requestFocus();
     }
-
-    @Test
-    public void onResume_setInitialText() {
-        mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON);
-        verify(mKeyguardMessageAreaController).setMessageIfEmpty(R.string.keyguard_enter_your_pin);
-    }
 }
-
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 9e5bfe5..d9efdea 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -98,6 +98,6 @@
     @Test
     fun startAppearAnimation() {
         pinViewController.startAppearAnimation()
-        verify(keyguardMessageAreaController).setMessageIfEmpty(R.string.keyguard_enter_your_pin)
+        verify(keyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pin)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 48e8239..b885d54 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -146,6 +146,8 @@
 
     @Captor
     private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
+    @Captor
+    private ArgumentCaptor<KeyguardSecurityContainer.SwipeListener> mSwipeListenerArgumentCaptor;
 
     private Configuration mConfiguration;
 
@@ -475,6 +477,64 @@
         verify(mKeyguardUpdateMonitor, never()).getUserHasTrust(anyInt());
     }
 
+    @Test
+    public void onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
+        KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
+                getRegisteredSwipeListener();
+        when(mKeyguardUpdateMonitor.isFaceDetectionRunning()).thenReturn(false);
+        setupGetSecurityView();
+
+        registeredSwipeListener.onSwipeUp();
+
+        verify(mKeyguardUpdateMonitor).requestFaceAuth(true,
+                FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
+    }
+
+    @Test
+    public void onSwipeUp_whenFaceDetectionIsRunning_doesNotInitiateFaceAuth() {
+        KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
+                getRegisteredSwipeListener();
+        when(mKeyguardUpdateMonitor.isFaceDetectionRunning()).thenReturn(true);
+
+        registeredSwipeListener.onSwipeUp();
+
+        verify(mKeyguardUpdateMonitor, never())
+                .requestFaceAuth(true,
+                        FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
+    }
+
+    @Test
+    public void onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() {
+        KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
+                getRegisteredSwipeListener();
+        when(mKeyguardUpdateMonitor.requestFaceAuth(true,
+                FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)).thenReturn(true);
+        setupGetSecurityView();
+
+        registeredSwipeListener.onSwipeUp();
+
+        verify(mKeyguardPasswordViewControllerMock).showMessage(null, null);
+    }
+
+    @Test
+    public void onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() {
+        KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
+                getRegisteredSwipeListener();
+        when(mKeyguardUpdateMonitor.requestFaceAuth(true,
+                FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)).thenReturn(false);
+        setupGetSecurityView();
+
+        registeredSwipeListener.onSwipeUp();
+
+        verify(mKeyguardPasswordViewControllerMock, never()).showMessage(null, null);
+    }
+
+    private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() {
+        mKeyguardSecurityContainerController.onViewAttached();
+        verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture());
+        return mSwipeListenerArgumentCaptor.getValue();
+    }
+
     private void setupConditionsToEnableSideFpsHint() {
         attachView();
         setSideFpsHintEnabledFromResources(true);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 4dcaa7c..c94c97c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -16,12 +16,16 @@
 
 package com.android.keyguard;
 
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.graphics.Rect;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.ClockAnimations;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -108,4 +112,16 @@
         configurationListenerArgumentCaptor.getValue().onLocaleListChanged();
         verify(mKeyguardClockSwitchController).onLocaleListChanged();
     }
+
+    @Test
+    public void getClockAnimations_forwardsToClockSwitch() {
+        ClockAnimations mockClockAnimations = mock(ClockAnimations.class);
+        when(mKeyguardClockSwitchController.getClockAnimations()).thenReturn(mockClockAnimations);
+
+        Rect r1 = new Rect(1, 2, 3, 4);
+        Rect r2 = new Rect(5, 6, 7, 8);
+        mController.getClockAnimations().onPositionUpdated(r1, r2, 0.3f);
+
+        verify(mockClockAnimations).onPositionUpdated(r1, r2, 0.3f);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 784e7dd..ebfb4d4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -25,6 +25,7 @@
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
+import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
 import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -647,6 +648,36 @@
                 KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
     }
 
+    @Test
+    public void requestFaceAuth_whenFaceAuthWasStarted_returnsTrue() throws RemoteException {
+        // This satisfies all the preconditions to run face auth.
+        keyguardNotGoingAway();
+        currentUserIsPrimary();
+        currentUserDoesNotHaveTrust();
+        biometricsNotDisabledThroughDevicePolicyManager();
+        biometricsEnabledForCurrentUser();
+        userNotCurrentlySwitching();
+        bouncerFullyVisibleAndNotGoingToSleep();
+        mTestableLooper.processAllMessages();
+
+        boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(true,
+                NOTIFICATION_PANEL_CLICKED);
+
+        assertThat(didFaceAuthRun).isTrue();
+    }
+
+    @Test
+    public void requestFaceAuth_whenFaceAuthWasNotStarted_returnsFalse() throws RemoteException {
+        // This ensures face auth won't run.
+        biometricsDisabledForCurrentUser();
+        mTestableLooper.processAllMessages();
+
+        boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(true,
+                NOTIFICATION_PANEL_CLICKED);
+
+        assertThat(didFaceAuthRun).isFalse();
+    }
+
     private void testStrongAuthExceptOnBouncer(int strongAuth) {
         when(mKeyguardBypassController.canBypass()).thenReturn(true);
         mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 2319f43..181839a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -36,6 +36,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isA;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -255,6 +256,7 @@
         });
         mScreenDecorations.mDisplayInfo = mDisplayInfo;
         doReturn(1f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio();
+        doNothing().when(mScreenDecorations).updateOverlayProviderViews(any());
         reset(mTunerService);
 
         try {
@@ -1005,18 +1007,13 @@
         assertEquals(new Size(3, 3), resDelegate.getTopRoundedSize());
         assertEquals(new Size(4, 4), resDelegate.getBottomRoundedSize());
 
-        setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
-                getTestsDrawable(com.android.systemui.tests.R.drawable.rounded4px)
-                /* roundedTopDrawable */,
-                getTestsDrawable(com.android.systemui.tests.R.drawable.rounded5px)
-                /* roundedBottomDrawable */,
-                0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning*/);
+        doReturn(2f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio();
         mDisplayInfo.rotation = Surface.ROTATION_270;
 
         mScreenDecorations.onConfigurationChanged(null);
 
-        assertEquals(new Size(4, 4), resDelegate.getTopRoundedSize());
-        assertEquals(new Size(5, 5), resDelegate.getBottomRoundedSize());
+        assertEquals(new Size(6, 6), resDelegate.getTopRoundedSize());
+        assertEquals(new Size(8, 8), resDelegate.getBottomRoundedSize());
     }
 
     @Test
@@ -1293,51 +1290,6 @@
     }
 
     @Test
-    public void testOnDisplayChanged_hwcLayer() {
-        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
-                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
-        final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
-        decorationSupport.format = PixelFormat.R_8;
-        doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();
-
-        // top cutout
-        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
-
-        mScreenDecorations.start();
-
-        final ScreenDecorHwcLayer hwcLayer = mScreenDecorations.mScreenDecorHwcLayer;
-        spyOn(hwcLayer);
-        doReturn(mDisplay).when(hwcLayer).getDisplay();
-
-        mScreenDecorations.mDisplayListener.onDisplayChanged(1);
-
-        verify(hwcLayer, times(1)).onDisplayChanged(any());
-    }
-
-    @Test
-    public void testOnDisplayChanged_nonHwcLayer() {
-        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
-                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
-
-        // top cutout
-        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
-
-        mScreenDecorations.start();
-
-        final ScreenDecorations.DisplayCutoutView cutoutView = (ScreenDecorations.DisplayCutoutView)
-                mScreenDecorations.getOverlayView(R.id.display_cutout);
-        assertNotNull(cutoutView);
-        spyOn(cutoutView);
-        doReturn(mDisplay).when(cutoutView).getDisplay();
-
-        mScreenDecorations.mDisplayListener.onDisplayChanged(1);
-
-        verify(cutoutView, times(1)).onDisplayChanged(any());
-    }
-
-    @Test
     public void testHasSameProvidersWithNullOverlays() {
         setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index 986e7cd..6ab54a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -935,6 +935,251 @@
         checkBounds(remainingChild, l = 0, t = 0, r = 100, b = 100)
     }
 
+    /* ******** start of animatesViewRemoval_includeMarginsTrue tests ******** */
+    @Test
+    fun animatesViewRemoval_includeMarginsTrue_center() {
+        setUpRootWithChildren(includeMarginsOnFirstChild = true)
+        val removedChild = rootView.getChildAt(0)
+        val originalLeft = removedChild.left
+        val originalTop = removedChild.top
+        val originalRight = removedChild.right
+        val originalBottom = removedChild.bottom
+
+        val success = ViewHierarchyAnimator.animateRemoval(
+            removedChild,
+            destination = ViewHierarchyAnimator.Hotspot.CENTER,
+            includeMargins = true,
+        )
+        forceLayout()
+
+        assertTrue(success)
+        assertNotNull(removedChild.getTag(R.id.tag_animator))
+        advanceAnimation(removedChild, 1.0f)
+        val expectedX = ((originalLeft - M_LEFT) + (originalRight + M_RIGHT)) / 2
+        val expectedY = ((originalTop - M_TOP) + (originalBottom + M_BOTTOM)) / 2
+
+        checkBounds(
+            removedChild,
+            l = expectedX,
+            t = expectedY,
+            r = expectedX,
+            b = expectedY
+        )
+    }
+
+    @Test
+    fun animatesViewRemoval_includeMarginsTrue_left() {
+        setUpRootWithChildren(includeMarginsOnFirstChild = true)
+        val removedChild = rootView.getChildAt(0)
+        val originalLeft = removedChild.left
+        val originalTop = removedChild.top
+        val originalBottom = removedChild.bottom
+
+        val success = ViewHierarchyAnimator.animateRemoval(
+            removedChild,
+            destination = ViewHierarchyAnimator.Hotspot.LEFT,
+            includeMargins = true,
+        )
+        forceLayout()
+
+        assertTrue(success)
+        assertNotNull(removedChild.getTag(R.id.tag_animator))
+        advanceAnimation(removedChild, 1.0f)
+        checkBounds(
+            removedChild,
+            l = originalLeft - M_LEFT,
+            t = originalTop,
+            r = originalLeft - M_LEFT,
+            b = originalBottom
+        )
+    }
+
+    @Test
+    fun animatesViewRemoval_includeMarginsTrue_topLeft() {
+        setUpRootWithChildren(includeMarginsOnFirstChild = true)
+        val removedChild = rootView.getChildAt(0)
+        val originalLeft = removedChild.left
+        val originalTop = removedChild.top
+
+        val success = ViewHierarchyAnimator.animateRemoval(
+            removedChild,
+            destination = ViewHierarchyAnimator.Hotspot.TOP_LEFT,
+            includeMargins = true,
+        )
+        forceLayout()
+
+        assertTrue(success)
+        assertNotNull(removedChild.getTag(R.id.tag_animator))
+        advanceAnimation(removedChild, 1.0f)
+        checkBounds(
+            removedChild,
+            l = originalLeft - M_LEFT,
+            t = originalTop - M_TOP,
+            r = originalLeft - M_LEFT,
+            b = originalTop - M_TOP
+        )
+    }
+
+    @Test
+    fun animatesViewRemoval_includeMarginsTrue_top() {
+        setUpRootWithChildren(includeMarginsOnFirstChild = true)
+        val removedChild = rootView.getChildAt(0)
+        val originalLeft = removedChild.left
+        val originalTop = removedChild.top
+        val originalRight = removedChild.right
+
+        val success = ViewHierarchyAnimator.animateRemoval(
+            removedChild,
+            destination = ViewHierarchyAnimator.Hotspot.TOP,
+            includeMargins = true,
+        )
+        forceLayout()
+
+        assertTrue(success)
+        assertNotNull(removedChild.getTag(R.id.tag_animator))
+        advanceAnimation(removedChild, 1.0f)
+        checkBounds(
+            removedChild,
+            l = originalLeft,
+            t = originalTop - M_TOP,
+            r = originalRight,
+            b = originalTop - M_TOP
+        )
+    }
+
+    @Test
+    fun animatesViewRemoval_includeMarginsTrue_topRight() {
+        setUpRootWithChildren(includeMarginsOnFirstChild = true)
+        val removedChild = rootView.getChildAt(0)
+        val originalTop = removedChild.top
+        val originalRight = removedChild.right
+
+        val success = ViewHierarchyAnimator.animateRemoval(
+            removedChild,
+            destination = ViewHierarchyAnimator.Hotspot.TOP_RIGHT,
+            includeMargins = true,
+        )
+        forceLayout()
+
+        assertTrue(success)
+        assertNotNull(removedChild.getTag(R.id.tag_animator))
+        advanceAnimation(removedChild, 1.0f)
+        checkBounds(
+            removedChild,
+            l = originalRight + M_RIGHT,
+            t = originalTop - M_TOP,
+            r = originalRight + M_RIGHT,
+            b = originalTop - M_TOP
+        )
+    }
+
+    @Test
+    fun animatesViewRemoval_includeMarginsTrue_right() {
+        setUpRootWithChildren(includeMarginsOnFirstChild = true)
+        val removedChild = rootView.getChildAt(0)
+        val originalTop = removedChild.top
+        val originalRight = removedChild.right
+        val originalBottom = removedChild.bottom
+
+        val success = ViewHierarchyAnimator.animateRemoval(
+            removedChild,
+            destination = ViewHierarchyAnimator.Hotspot.RIGHT,
+            includeMargins = true,
+        )
+        forceLayout()
+
+        assertTrue(success)
+        assertNotNull(removedChild.getTag(R.id.tag_animator))
+        advanceAnimation(removedChild, 1.0f)
+        checkBounds(
+            removedChild,
+            l = originalRight + M_RIGHT,
+            t = originalTop,
+            r = originalRight + M_RIGHT,
+            b = originalBottom
+        )
+    }
+
+    @Test
+    fun animatesViewRemoval_includeMarginsTrue_bottomRight() {
+        setUpRootWithChildren(includeMarginsOnFirstChild = true)
+        val removedChild = rootView.getChildAt(0)
+        val originalRight = removedChild.right
+        val originalBottom = removedChild.bottom
+
+        val success = ViewHierarchyAnimator.animateRemoval(
+            removedChild,
+            destination = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT,
+            includeMargins = true,
+        )
+        forceLayout()
+
+        assertTrue(success)
+        assertNotNull(removedChild.getTag(R.id.tag_animator))
+        advanceAnimation(removedChild, 1.0f)
+        checkBounds(
+            removedChild,
+            l = originalRight + M_RIGHT,
+            t = originalBottom + M_BOTTOM,
+            r = originalRight + M_RIGHT,
+            b = originalBottom + M_BOTTOM
+        )
+    }
+
+    @Test
+    fun animatesViewRemoval_includeMarginsTrue_bottom() {
+        setUpRootWithChildren(includeMarginsOnFirstChild = true)
+        val removedChild = rootView.getChildAt(0)
+        val originalLeft = removedChild.left
+        val originalRight = removedChild.right
+        val originalBottom = removedChild.bottom
+
+        val success = ViewHierarchyAnimator.animateRemoval(
+            removedChild,
+            destination = ViewHierarchyAnimator.Hotspot.BOTTOM,
+            includeMargins = true,
+        )
+        forceLayout()
+
+        assertTrue(success)
+        assertNotNull(removedChild.getTag(R.id.tag_animator))
+        advanceAnimation(removedChild, 1.0f)
+        checkBounds(
+            removedChild,
+            l = originalLeft,
+            t = originalBottom + M_BOTTOM,
+            r = originalRight,
+            b = originalBottom + M_BOTTOM
+        )
+    }
+
+    @Test
+    fun animatesViewRemoval_includeMarginsTrue_bottomLeft() {
+        setUpRootWithChildren(includeMarginsOnFirstChild = true)
+        val removedChild = rootView.getChildAt(0)
+        val originalLeft = removedChild.left
+        val originalBottom = removedChild.bottom
+
+        val success = ViewHierarchyAnimator.animateRemoval(
+            removedChild,
+            destination = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT,
+            includeMargins = true,
+        )
+        forceLayout()
+
+        assertTrue(success)
+        assertNotNull(removedChild.getTag(R.id.tag_animator))
+        advanceAnimation(removedChild, 1.0f)
+        checkBounds(
+            removedChild,
+            l = originalLeft - M_LEFT,
+            t = originalBottom + M_BOTTOM,
+            r = originalLeft - M_LEFT,
+            b = originalBottom + M_BOTTOM
+        )
+    }
+    /* ******** end of animatesViewRemoval_includeMarginsTrue tests ******** */
+
     @Test
     fun animatesChildrenDuringViewRemoval() {
         setUpRootWithChildren()
@@ -1215,7 +1460,7 @@
         checkBounds(rootView, l = 10, t = 10, r = 50, b = 50)
     }
 
-    private fun setUpRootWithChildren() {
+    private fun setUpRootWithChildren(includeMarginsOnFirstChild: Boolean = false) {
         rootView = LinearLayout(mContext)
         (rootView as LinearLayout).orientation = LinearLayout.HORIZONTAL
         (rootView as LinearLayout).weightSum = 1f
@@ -1229,13 +1474,26 @@
         val secondChild = View(mContext)
         rootView.addView(secondChild)
 
-        val childParams = LinearLayout.LayoutParams(
+        val firstChildParams = LinearLayout.LayoutParams(
             0 /* width */,
             LinearLayout.LayoutParams.MATCH_PARENT
         )
-        childParams.weight = 0.5f
-        firstChild.layoutParams = childParams
-        secondChild.layoutParams = childParams
+        firstChildParams.weight = 0.5f
+        if (includeMarginsOnFirstChild) {
+            firstChildParams.leftMargin = M_LEFT
+            firstChildParams.topMargin = M_TOP
+            firstChildParams.rightMargin = M_RIGHT
+            firstChildParams.bottomMargin = M_BOTTOM
+        }
+        firstChild.layoutParams = firstChildParams
+
+        val secondChildParams = LinearLayout.LayoutParams(
+            0 /* width */,
+            LinearLayout.LayoutParams.MATCH_PARENT
+        )
+        secondChildParams.weight = 0.5f
+        secondChild.layoutParams = secondChildParams
+
         firstGrandChild.layoutParams = RelativeLayout.LayoutParams(40 /* width */, 40 /* height */)
         (firstGrandChild.layoutParams as RelativeLayout.LayoutParams)
             .addRule(RelativeLayout.ALIGN_PARENT_START)
@@ -1315,3 +1573,9 @@
         }
     }
 }
+
+// Margin values.
+private const val M_LEFT = 14
+private const val M_TOP = 16
+private const val M_RIGHT = 18
+private const val M_BOTTOM = 20
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index baeabc5..c85334d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -26,6 +26,7 @@
 import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
 import android.hardware.fingerprint.FingerprintManager
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
+import android.provider.Settings
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.LayoutInflater
@@ -124,14 +125,18 @@
         whenever(udfpsEnrollView.context).thenReturn(context)
     }
 
-    private fun withReason(@ShowReason reason: Int, block: () -> Unit) {
+    private fun withReason(
+        @ShowReason reason: Int,
+        isDebuggable: Boolean = false,
+        block: () -> Unit
+    ) {
         controllerOverlay = UdfpsControllerOverlay(
             context, fingerprintManager, inflater, windowManager, accessibilityManager,
             statusBarStateController, shadeExpansionStateManager, statusBarKeyguardViewManager,
             keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
             configurationController, systemClock, keyguardStateController,
             unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason,
-            controllerCallback, onTouch, activityLaunchAnimator
+            controllerCallback, onTouch, activityLaunchAnimator, isDebuggable
         )
         block()
     }
@@ -151,11 +156,29 @@
     }
 
     @Test
+    fun showUdfpsOverlay_locate_withEnrollmentUiRemoved() {
+        Settings.Global.putInt(mContext.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, 1)
+        withReason(REASON_ENROLL_FIND_SENSOR, isDebuggable = true) {
+            showUdfpsOverlay(isEnrollUseCase = false)
+        }
+        Settings.Global.putInt(mContext.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, 0)
+    }
+
+    @Test
     fun showUdfpsOverlay_enroll() = withReason(REASON_ENROLL_ENROLLING) {
         showUdfpsOverlay(isEnrollUseCase = true)
     }
 
     @Test
+    fun showUdfpsOverlay_enroll_withEnrollmentUiRemoved() {
+        Settings.Global.putInt(mContext.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, 1)
+        withReason(REASON_ENROLL_ENROLLING, isDebuggable = true) {
+            showUdfpsOverlay(isEnrollUseCase = false)
+        }
+        Settings.Global.putInt(mContext.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, 0)
+    }
+
+    @Test
     fun showUdfpsOverlay_other() = withReason(REASON_AUTH_OTHER) { showUdfpsOverlay() }
 
     private fun withRotation(@Rotation rotation: Int, block: () -> Unit) {
@@ -372,21 +395,33 @@
             context.resources.getStringArray(R.array.udfps_accessibility_touch_hints)
         val rotation = Surface.ROTATION_0
         // touch at 0 degrees
-        assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 0.0f /* y */,
-                0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
-                .isEqualTo(touchHints[0])
+        assertThat(
+            controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+                0.0f /* x */, 0.0f /* y */,
+                0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+            )
+        ).isEqualTo(touchHints[0])
         // touch at 90 degrees
-        assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, -1.0f /* y */,
-                0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
-                .isEqualTo(touchHints[1])
+        assertThat(
+            controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+                0.0f /* x */, -1.0f /* y */,
+                0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+            )
+        ).isEqualTo(touchHints[1])
         // touch at 180 degrees
-        assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(-1.0f /* x */, 0.0f /* y */,
-                0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
-                .isEqualTo(touchHints[2])
+        assertThat(
+            controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+                -1.0f /* x */, 0.0f /* y */,
+                0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+            )
+        ).isEqualTo(touchHints[2])
         // touch at 270 degrees
-        assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 1.0f /* y */,
-                0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
-                .isEqualTo(touchHints[3])
+        assertThat(
+            controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+                0.0f /* x */, 1.0f /* y */,
+                0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+            )
+        ).isEqualTo(touchHints[3])
     }
 
     fun testTouchOutsideAreaNoRotation90Degrees() = withReason(REASON_ENROLL_ENROLLING) {
@@ -394,21 +429,33 @@
             context.resources.getStringArray(R.array.udfps_accessibility_touch_hints)
         val rotation = Surface.ROTATION_90
         // touch at 0 degrees -> 90 degrees
-        assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 0.0f /* y */,
-                0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
-                .isEqualTo(touchHints[1])
+        assertThat(
+            controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+                0.0f /* x */, 0.0f /* y */,
+                0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+            )
+        ).isEqualTo(touchHints[1])
         // touch at 90 degrees -> 180 degrees
-        assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, -1.0f /* y */,
-                0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
-                .isEqualTo(touchHints[2])
+        assertThat(
+            controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+                0.0f /* x */, -1.0f /* y */,
+                0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+            )
+        ).isEqualTo(touchHints[2])
         // touch at 180 degrees -> 270 degrees
-        assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(-1.0f /* x */, 0.0f /* y */,
-                0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
-                .isEqualTo(touchHints[3])
+        assertThat(
+            controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+                -1.0f /* x */, 0.0f /* y */,
+                0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+            )
+        ).isEqualTo(touchHints[3])
         // touch at 270 degrees -> 0 degrees
-        assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 1.0f /* y */,
-                0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
-                .isEqualTo(touchHints[0])
+        assertThat(
+            controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+                0.0f /* x */, 1.0f /* y */,
+                0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+            )
+        ).isEqualTo(touchHints[0])
     }
 
     fun testTouchOutsideAreaNoRotation270Degrees() = withReason(REASON_ENROLL_ENROLLING) {
@@ -416,21 +463,33 @@
             context.resources.getStringArray(R.array.udfps_accessibility_touch_hints)
         val rotation = Surface.ROTATION_270
         // touch at 0 degrees -> 270 degrees
-        assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 0.0f /* y */,
-                0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
-                .isEqualTo(touchHints[3])
+        assertThat(
+            controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+                0.0f /* x */, 0.0f /* y */,
+                0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+            )
+        ).isEqualTo(touchHints[3])
         // touch at 90 degrees -> 0 degrees
-        assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, -1.0f /* y */,
-                0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
-                .isEqualTo(touchHints[0])
+        assertThat(
+            controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+                0.0f /* x */, -1.0f /* y */,
+                0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+            )
+        ).isEqualTo(touchHints[0])
         // touch at 180 degrees -> 90 degrees
-        assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(-1.0f /* x */, 0.0f /* y */,
-                0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
-                .isEqualTo(touchHints[1])
+        assertThat(
+            controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+                -1.0f /* x */, 0.0f /* y */,
+                0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+            )
+        ).isEqualTo(touchHints[1])
         // touch at 270 degrees -> 180 degrees
-        assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 1.0f /* y */,
-                0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
-                .isEqualTo(touchHints[2])
+        assertThat(
+            controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+                0.0f /* x */, 1.0f /* y */,
+                0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+            )
+        ).isEqualTo(touchHints[2])
     }
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 8923ba8..28e13b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -169,6 +169,8 @@
     @Mock
     private LatencyTracker mLatencyTracker;
     private FakeExecutor mFgExecutor;
+    @Mock
+    private UdfpsDisplayMode mUdfpsDisplayMode;
 
     // Stuff for configuring mocks
     @Mock
@@ -258,7 +260,6 @@
                 mVibrator,
                 mUdfpsHapticsSimulator,
                 mUdfpsShell,
-                Optional.of(mDisplayModeProvider),
                 mKeyguardStateController,
                 mDisplayManager,
                 mHandler,
@@ -275,6 +276,7 @@
         verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
         mScreenObserver = mScreenObserverCaptor.getValue();
         mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, new UdfpsOverlayParams());
+        mUdfpsController.setUdfpsDisplayMode(mUdfpsDisplayMode);
     }
 
     @Test
@@ -659,7 +661,7 @@
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
         when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
         // WHEN it is cancelled
-        mUdfpsController.onCancelUdfps();
+        mUdfpsController.cancelAodInterrupt();
         // THEN the display is unconfigured
         verify(mUdfpsView).unconfigureDisplay();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
new file mode 100644
index 0000000..7e35b26
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.biometrics;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecution;
+
+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)
+@RunWithLooper(setAsMainLooper = true)
+public class UdfpsDisplayModeTest extends SysuiTestCase {
+    private static final int DISPLAY_ID = 0;
+
+    @Mock
+    private AuthController mAuthController;
+    @Mock
+    private IUdfpsHbmListener mDisplayCallback;
+    @Mock
+    private Runnable mOnEnabled;
+    @Mock
+    private Runnable mOnDisabled;
+
+    private final FakeExecution mExecution = new FakeExecution();
+    private UdfpsDisplayMode mHbmController;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        // Force mContext to always return DISPLAY_ID
+        Context contextSpy = spy(mContext);
+        when(contextSpy.getDisplayId()).thenReturn(DISPLAY_ID);
+
+        // Set up mocks.
+        when(mAuthController.getUdfpsHbmListener()).thenReturn(mDisplayCallback);
+
+        // Create a real controller with mock dependencies.
+        mHbmController = new UdfpsDisplayMode(contextSpy, mExecution, mAuthController);
+    }
+
+    @Test
+    public void roundTrip() throws RemoteException {
+        // Enable the UDFPS mode.
+        mHbmController.enable(mOnEnabled);
+
+        // Should set the appropriate refresh rate for UDFPS and notify the caller.
+        verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID));
+        verify(mOnEnabled).run();
+
+        // Disable the UDFPS mode.
+        mHbmController.disable(mOnDisabled);
+
+        // Should unset the refresh rate and notify the caller.
+        verify(mOnDisabled).run();
+        verify(mDisplayCallback).onHbmDisabled(eq(DISPLAY_ID));
+    }
+
+    @Test
+    public void mustNotEnableMoreThanOnce() throws RemoteException {
+        // First request to enable the UDFPS mode.
+        mHbmController.enable(mOnEnabled);
+
+        // Should set the appropriate refresh rate for UDFPS and notify the caller.
+        verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID));
+        verify(mOnEnabled).run();
+
+        // Second request to enable the UDFPS mode, while it's still enabled.
+        mHbmController.enable(mOnEnabled);
+
+        // Should ignore the second request.
+        verifyNoMoreInteractions(mDisplayCallback);
+        verifyNoMoreInteractions(mOnEnabled);
+    }
+
+    @Test
+    public void mustNotDisableMoreThanOnce() throws RemoteException {
+        // Disable the UDFPS mode.
+        mHbmController.enable(mOnEnabled);
+
+        // Should set the appropriate refresh rate for UDFPS and notify the caller.
+        verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID));
+        verify(mOnEnabled).run();
+
+        // First request to disable the UDFPS mode.
+        mHbmController.disable(mOnDisabled);
+
+        // Should unset the refresh rate and notify the caller.
+        verify(mOnDisabled).run();
+        verify(mDisplayCallback).onHbmDisabled(eq(DISPLAY_ID));
+
+        // Second request to disable the UDFPS mode, when it's already disabled.
+        mHbmController.disable(mOnDisabled);
+
+        // Should ignore the second request.
+        verifyNoMoreInteractions(mOnDisabled);
+        verifyNoMoreInteractions(mDisplayCallback);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index b2a9e82..6bc7308 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -145,6 +145,35 @@
     }
 
     @Test
+    public void testIsFalseTouch_SeekBar_FalseTouch() {
+        when(mClassifierA.classifyGesture(anyInt(), anyDouble(), anyDouble()))
+                .thenReturn(mFalsedResult);
+        when(mSingleTapClassfier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult);
+        assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).isTrue();
+    }
+
+    @Test
+    public void testIsFalseTouch_SeekBar_RealTouch() {
+        when(mSingleTapClassfier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult);
+        assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).isFalse();
+    }
+
+    @Test
+    public void testIsFalseTouch_SeekBar_FalseTap() {
+        when(mClassifierA.classifyGesture(anyInt(), anyDouble(), anyDouble()))
+                .thenReturn(mFalsedResult);
+        when(mSingleTapClassfier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult);
+        assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).isTrue();
+    }
+
+    @Test
+    public void testIsFalseTouch_SeekBar_RealTap() {
+        when(mClassifierA.classifyGesture(anyInt(), anyDouble(), anyDouble()))
+                .thenReturn(mFalsedResult);
+        assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).isFalse();
+    }
+
+    @Test
     public void testIsFalseTouch_ClassifierBRejects() {
         when(mClassifierB.classifyGesture(anyInt(), anyDouble(), anyDouble()))
                 .thenReturn(mFalsedResult);
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 3e9cf1e..fa9c41a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -35,6 +35,7 @@
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerFake;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -71,6 +72,8 @@
     @Mock
     private KeyguardStateController mKeyguardStateController;
     @Mock
+    private ShadeExpansionStateManager mShadeExpansionStateManager;
+    @Mock
     private BatteryController mBatteryController;
     private final DockManagerFake mDockManager = new DockManagerFake();
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
@@ -85,7 +88,8 @@
 
         mFalsingCollector = new FalsingCollectorImpl(mFalsingDataProvider, mFalsingManager,
                 mKeyguardUpdateMonitor, mHistoryTracker, mProximitySensor,
-                mStatusBarStateController, mKeyguardStateController, mBatteryController,
+                mStatusBarStateController, mKeyguardStateController, mShadeExpansionStateManager,
+                mBatteryController,
                 mDockManager, mFakeExecutor, mFakeSystemClock);
     }
 
@@ -137,9 +141,9 @@
     public void testUnregisterSensor_QS() {
         mFalsingCollector.onScreenTurningOn();
         reset(mProximitySensor);
-        mFalsingCollector.setQsExpanded(true);
+        mFalsingCollector.onQsExpansionChanged(true);
         verify(mProximitySensor).unregister(any(ThresholdSensor.Listener.class));
-        mFalsingCollector.setQsExpanded(false);
+        mFalsingCollector.onQsExpansionChanged(false);
         verify(mProximitySensor).register(any(ThresholdSensor.Listener.class));
     }
 
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 91214a8..e7e6918 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -38,6 +38,8 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.util.DeviceConfigProxyFake;
 
 import org.junit.Before;
@@ -47,6 +49,9 @@
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import javax.inject.Provider;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -55,11 +60,15 @@
     @Mock
     private ClipboardManager mClipboardManager;
     @Mock
-    private ClipboardOverlayControllerFactory mClipboardOverlayControllerFactory;
+    private ClipboardOverlayControllerLegacyFactory mClipboardOverlayControllerLegacyFactory;
+    @Mock
+    private ClipboardOverlayControllerLegacy mOverlayControllerLegacy;
     @Mock
     private ClipboardOverlayController mOverlayController;
     @Mock
     private UiEventLogger mUiEventLogger;
+    @Mock
+    private FeatureFlags mFeatureFlags;
     private DeviceConfigProxyFake mDeviceConfigProxy;
 
     private ClipData mSampleClipData;
@@ -72,12 +81,17 @@
     @Captor
     private ArgumentCaptor<String> mStringCaptor;
 
+    @Spy
+    private Provider<ClipboardOverlayController> mOverlayControllerProvider;
+
 
     @Before
     public void setup() {
+        mOverlayControllerProvider = () -> mOverlayController;
+
         MockitoAnnotations.initMocks(this);
-        when(mClipboardOverlayControllerFactory.create(any())).thenReturn(
-                mOverlayController);
+        when(mClipboardOverlayControllerLegacyFactory.create(any()))
+                .thenReturn(mOverlayControllerLegacy);
         when(mClipboardManager.hasPrimaryClip()).thenReturn(true);
 
 
@@ -94,7 +108,8 @@
         mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
                 "false", false);
         ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
-                mClipboardOverlayControllerFactory, mClipboardManager, mUiEventLogger);
+                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+                mClipboardManager, mUiEventLogger, mFeatureFlags);
         listener.start();
         verifyZeroInteractions(mClipboardManager);
         verifyZeroInteractions(mUiEventLogger);
@@ -105,7 +120,8 @@
         mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
                 "true", false);
         ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
-                mClipboardOverlayControllerFactory, mClipboardManager, mUiEventLogger);
+                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+                mClipboardManager, mUiEventLogger, mFeatureFlags);
         listener.start();
         verify(mClipboardManager).addPrimaryClipChangedListener(any());
         verifyZeroInteractions(mUiEventLogger);
@@ -113,16 +129,58 @@
 
     @Test
     public void test_consecutiveCopies() {
+        when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(false);
+
         mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
                 "true", false);
         ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
-                mClipboardOverlayControllerFactory, mClipboardManager, mUiEventLogger);
+                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+                mClipboardManager, mUiEventLogger, mFeatureFlags);
         listener.start();
         listener.onPrimaryClipChanged();
 
-        verify(mClipboardOverlayControllerFactory).create(any());
+        verify(mClipboardOverlayControllerLegacyFactory).create(any());
 
-        verify(mOverlayController).setClipData(mClipDataCaptor.capture(), mStringCaptor.capture());
+        verify(mOverlayControllerLegacy).setClipData(
+                mClipDataCaptor.capture(), mStringCaptor.capture());
+
+        assertEquals(mSampleClipData, mClipDataCaptor.getValue());
+        assertEquals(mSampleSource, mStringCaptor.getValue());
+
+        verify(mOverlayControllerLegacy).setOnSessionCompleteListener(mRunnableCaptor.capture());
+
+        // Should clear the overlay controller
+        mRunnableCaptor.getValue().run();
+
+        listener.onPrimaryClipChanged();
+
+        verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any());
+
+        // Not calling the runnable here, just change the clip again and verify that the overlay is
+        // NOT recreated.
+
+        listener.onPrimaryClipChanged();
+
+        verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any());
+        verifyZeroInteractions(mOverlayControllerProvider);
+    }
+
+    @Test
+    public void test_consecutiveCopies_new() {
+        when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(true);
+
+        mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
+                "true", false);
+        ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
+                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+                mClipboardManager, mUiEventLogger, mFeatureFlags);
+        listener.start();
+        listener.onPrimaryClipChanged();
+
+        verify(mOverlayControllerProvider).get();
+
+        verify(mOverlayController).setClipData(
+                mClipDataCaptor.capture(), mStringCaptor.capture());
 
         assertEquals(mSampleClipData, mClipDataCaptor.getValue());
         assertEquals(mSampleSource, mStringCaptor.getValue());
@@ -134,14 +192,15 @@
 
         listener.onPrimaryClipChanged();
 
-        verify(mClipboardOverlayControllerFactory, times(2)).create(any());
+        verify(mOverlayControllerProvider, times(2)).get();
 
         // Not calling the runnable here, just change the clip again and verify that the overlay is
         // NOT recreated.
 
         listener.onPrimaryClipChanged();
 
-        verify(mClipboardOverlayControllerFactory, times(2)).create(any());
+        verify(mOverlayControllerProvider, times(2)).get();
+        verifyZeroInteractions(mClipboardOverlayControllerLegacyFactory);
     }
 
     @Test
@@ -169,4 +228,40 @@
         assertTrue(ClipboardListener.shouldSuppressOverlay(suppressableClipData,
                 ClipboardListener.SHELL_PACKAGE, false));
     }
+
+    @Test
+    public void test_logging_enterAndReenter() {
+        when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(false);
+
+        ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
+                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+                mClipboardManager, mUiEventLogger, mFeatureFlags);
+        listener.start();
+
+        listener.onPrimaryClipChanged();
+        listener.onPrimaryClipChanged();
+
+        verify(mUiEventLogger, times(1)).log(
+                ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
+        verify(mUiEventLogger, times(1)).log(
+                ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED, 0, mSampleSource);
+    }
+
+    @Test
+    public void test_logging_enterAndReenter_new() {
+        when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(true);
+
+        ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
+                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+                mClipboardManager, mUiEventLogger, mFeatureFlags);
+        listener.start();
+
+        listener.onPrimaryClipChanged();
+        listener.onPrimaryClipChanged();
+
+        verify(mUiEventLogger, times(1)).log(
+                ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
+        verify(mUiEventLogger, times(1)).log(
+                ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED, 0, mSampleSource);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
new file mode 100644
index 0000000..b7f1c1a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.clipboardoverlay;
+
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.Animator;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.net.Uri;
+import android.os.PersistableBundle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.screenshot.TimeoutHandler;
+
+import org.junit.After;
+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;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ClipboardOverlayControllerTest extends SysuiTestCase {
+
+    private ClipboardOverlayController mOverlayController;
+    @Mock
+    private ClipboardOverlayView mClipboardOverlayView;
+    @Mock
+    private ClipboardOverlayWindow mClipboardOverlayWindow;
+    @Mock
+    private BroadcastSender mBroadcastSender;
+    @Mock
+    private TimeoutHandler mTimeoutHandler;
+    @Mock
+    private UiEventLogger mUiEventLogger;
+
+    @Mock
+    private Animator mAnimator;
+
+    private ClipData mSampleClipData;
+
+    @Captor
+    private ArgumentCaptor<ClipboardOverlayView.ClipboardOverlayCallbacks> mOverlayCallbacksCaptor;
+    private ClipboardOverlayView.ClipboardOverlayCallbacks mCallbacks;
+
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mClipboardOverlayView.getEnterAnimation()).thenReturn(mAnimator);
+        when(mClipboardOverlayView.getExitAnimation()).thenReturn(mAnimator);
+
+        mSampleClipData = new ClipData("Test", new String[]{"text/plain"},
+                new ClipData.Item("Test Item"));
+
+        mOverlayController = new ClipboardOverlayController(
+                mContext,
+                mClipboardOverlayView,
+                mClipboardOverlayWindow,
+                getFakeBroadcastDispatcher(),
+                mBroadcastSender,
+                mTimeoutHandler,
+                mUiEventLogger);
+        verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
+        mCallbacks = mOverlayCallbacksCaptor.getValue();
+    }
+
+    @After
+    public void tearDown() {
+        mOverlayController.hideImmediate();
+    }
+
+    @Test
+    public void test_setClipData_nullData() {
+        ClipData clipData = null;
+        mOverlayController.setClipData(clipData, "");
+
+        verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+        verify(mClipboardOverlayView, times(0)).showShareChip();
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_setClipData_invalidImageData() {
+        ClipData clipData = new ClipData("", new String[]{"image/png"},
+                new ClipData.Item(Uri.parse("")));
+
+        mOverlayController.setClipData(clipData, "");
+
+        verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+        verify(mClipboardOverlayView, times(0)).showShareChip();
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_setClipData_textData() {
+        mOverlayController.setClipData(mSampleClipData, "");
+
+        verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false);
+        verify(mClipboardOverlayView, times(1)).showShareChip();
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_setClipData_sensitiveTextData() {
+        ClipDescription description = mSampleClipData.getDescription();
+        PersistableBundle b = new PersistableBundle();
+        b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
+        description.setExtras(b);
+        ClipData data = new ClipData(description, mSampleClipData.getItemAt(0));
+        mOverlayController.setClipData(data, "");
+
+        verify(mClipboardOverlayView, times(1)).showTextPreview("••••••", true);
+        verify(mClipboardOverlayView, times(1)).showShareChip();
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_setClipData_repeatedCalls() {
+        when(mAnimator.isRunning()).thenReturn(true);
+
+        mOverlayController.setClipData(mSampleClipData, "");
+        mOverlayController.setClipData(mSampleClipData, "");
+
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_viewCallbacks_onShareTapped() {
+        mOverlayController.setClipData(mSampleClipData, "");
+
+        mCallbacks.onShareButtonTapped();
+
+        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED);
+        verify(mClipboardOverlayView, times(1)).getExitAnimation();
+    }
+
+    @Test
+    public void test_viewCallbacks_onDismissTapped() {
+        mOverlayController.setClipData(mSampleClipData, "");
+
+        mCallbacks.onDismissButtonTapped();
+
+        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+        verify(mClipboardOverlayView, times(1)).getExitAnimation();
+    }
+
+    @Test
+    public void test_multipleDismissals_dismissesOnce() {
+        mCallbacks.onSwipeDismissInitiated(mAnimator);
+        mCallbacks.onDismissButtonTapped();
+        mCallbacks.onSwipeDismissInitiated(mAnimator);
+        mCallbacks.onDismissButtonTapped();
+
+        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
+        verify(mUiEventLogger, never()).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEventTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEventTest.java
deleted file mode 100644
index c7c2cd8d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEventTest.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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.clipboardoverlay;
-
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.provider.DeviceConfig;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.DeviceConfigProxyFake;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class ClipboardOverlayEventTest extends SysuiTestCase {
-
-    @Mock
-    private ClipboardManager mClipboardManager;
-    @Mock
-    private ClipboardOverlayControllerFactory mClipboardOverlayControllerFactory;
-    @Mock
-    private ClipboardOverlayController mOverlayController;
-    @Mock
-    private UiEventLogger mUiEventLogger;
-
-    private final String mSampleSource = "Example source";
-
-    private ClipboardListener mClipboardListener;
-
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        when(mClipboardOverlayControllerFactory.create(any())).thenReturn(
-                mOverlayController);
-        when(mClipboardManager.hasPrimaryClip()).thenReturn(true);
-
-        ClipData sampleClipData = new ClipData("Test", new String[]{"text/plain"},
-                new ClipData.Item("Test Item"));
-        when(mClipboardManager.getPrimaryClip()).thenReturn(sampleClipData);
-        when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
-
-        DeviceConfigProxyFake deviceConfigProxy = new DeviceConfigProxyFake();
-        deviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
-                "true", false);
-
-        mClipboardListener = new ClipboardListener(getContext(), deviceConfigProxy,
-                mClipboardOverlayControllerFactory, mClipboardManager, mUiEventLogger);
-    }
-
-    @Test
-    public void test_enterAndReenter() {
-        mClipboardListener.start();
-
-        mClipboardListener.onPrimaryClipChanged();
-        mClipboardListener.onPrimaryClipChanged();
-
-        verify(mUiEventLogger, times(1)).log(
-                ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
-        verify(mUiEventLogger, times(1)).log(
-                ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED, 0, mSampleSource);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
index f933361..93a1868 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
@@ -24,12 +24,11 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.R as InternalR
 import com.android.systemui.R as SystemUIR
-import com.android.systemui.tests.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.tests.R
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
-
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
@@ -102,14 +101,11 @@
         assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize)
         assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize)
 
-        setupResources(radius = 100,
-                roundedTopDrawable = getTestsDrawable(R.drawable.rounded4px),
-                roundedBottomDrawable = getTestsDrawable(R.drawable.rounded5px))
-
+        roundedCornerResDelegate.physicalPixelDisplaySizeRatio = 2f
         roundedCornerResDelegate.updateDisplayUniqueId(null, 1)
 
-        assertEquals(Size(4, 4), roundedCornerResDelegate.topRoundedSize)
-        assertEquals(Size(5, 5), roundedCornerResDelegate.bottomRoundedSize)
+        assertEquals(Size(6, 6), roundedCornerResDelegate.topRoundedSize)
+        assertEquals(Size(8, 8), roundedCornerResDelegate.bottomRoundedSize)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index 6a55a60..5bbd810 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.doze;
 
+import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
+import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
+
 import static com.android.systemui.doze.DozeMachine.State.DOZE;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED;
@@ -38,16 +41,17 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.UiModeManager;
 import android.content.res.Configuration;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.testing.AndroidTestingRunner;
 import android.testing.UiThreadTest;
 import android.view.Display;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -78,25 +82,30 @@
     @Mock
     private DozeHost mHost;
     @Mock
-    private UiModeManager mUiModeManager;
+    private DozeMachine.Part mPartMock;
+    @Mock
+    private DozeMachine.Part mAnotherPartMock;
     private DozeServiceFake mServiceFake;
     private WakeLockFake mWakeLockFake;
-    private AmbientDisplayConfiguration mConfigMock;
-    private DozeMachine.Part mPartMock;
+    private AmbientDisplayConfiguration mAmbientDisplayConfigMock;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mServiceFake = new DozeServiceFake();
         mWakeLockFake = new WakeLockFake();
-        mConfigMock = mock(AmbientDisplayConfiguration.class);
-        mPartMock = mock(DozeMachine.Part.class);
+        mAmbientDisplayConfigMock = mock(AmbientDisplayConfiguration.class);
         when(mDockManager.isDocked()).thenReturn(false);
         when(mDockManager.isHidden()).thenReturn(false);
 
-        mMachine = new DozeMachine(mServiceFake, mConfigMock, mWakeLockFake,
-                mWakefulnessLifecycle, mUiModeManager, mDozeLog, mDockManager,
-                mHost, new DozeMachine.Part[]{mPartMock});
+        mMachine = new DozeMachine(mServiceFake,
+                mAmbientDisplayConfigMock,
+                mWakeLockFake,
+                mWakefulnessLifecycle,
+                mDozeLog,
+                mDockManager,
+                mHost,
+                new DozeMachine.Part[]{mPartMock, mAnotherPartMock});
     }
 
     @Test
@@ -108,7 +117,7 @@
 
     @Test
     public void testInitialize_goesToDoze() {
-        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
+        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
 
         mMachine.requestState(INITIALIZED);
 
@@ -118,7 +127,7 @@
 
     @Test
     public void testInitialize_goesToAod() {
-        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
 
         mMachine.requestState(INITIALIZED);
 
@@ -138,7 +147,7 @@
 
     @Test
     public void testInitialize_afterDockPaused_goesToDoze() {
-        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
         when(mDockManager.isDocked()).thenReturn(true);
         when(mDockManager.isHidden()).thenReturn(true);
 
@@ -151,7 +160,7 @@
     @Test
     public void testInitialize_alwaysOnSuppressed_alwaysOnDisabled_goesToDoze() {
         when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
-        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
+        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
 
         mMachine.requestState(INITIALIZED);
 
@@ -162,7 +171,7 @@
     @Test
     public void testInitialize_alwaysOnSuppressed_alwaysOnEnabled_goesToDoze() {
         when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
-        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
 
         mMachine.requestState(INITIALIZED);
 
@@ -184,7 +193,7 @@
     @Test
     public void testInitialize_alwaysOnSuppressed_alwaysOnDisabled_afterDockPaused_goesToDoze() {
         when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
-        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
+        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
         when(mDockManager.isDocked()).thenReturn(true);
         when(mDockManager.isHidden()).thenReturn(true);
 
@@ -197,7 +206,7 @@
     @Test
     public void testInitialize_alwaysOnSuppressed_alwaysOnEnabled_afterDockPaused_goesToDoze() {
         when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
-        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
         when(mDockManager.isDocked()).thenReturn(true);
         when(mDockManager.isHidden()).thenReturn(true);
 
@@ -209,7 +218,7 @@
 
     @Test
     public void testPulseDone_goesToDoze() {
-        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
+        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
         mMachine.requestState(INITIALIZED);
         mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
         mMachine.requestState(DOZE_PULSING);
@@ -222,7 +231,7 @@
 
     @Test
     public void testPulseDone_goesToAoD() {
-        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
         mMachine.requestState(INITIALIZED);
         mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
         mMachine.requestState(DOZE_PULSING);
@@ -236,7 +245,7 @@
     @Test
     public void testPulseDone_alwaysOnSuppressed_goesToSuppressed() {
         when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
-        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
         mMachine.requestState(INITIALIZED);
         mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
         mMachine.requestState(DOZE_PULSING);
@@ -287,7 +296,7 @@
 
     @Test
     public void testPulseDone_afterDockPaused_goesToDoze() {
-        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
         when(mDockManager.isDocked()).thenReturn(true);
         when(mDockManager.isHidden()).thenReturn(true);
         mMachine.requestState(INITIALIZED);
@@ -303,7 +312,7 @@
     @Test
     public void testPulseDone_alwaysOnSuppressed_afterDockPaused_goesToDoze() {
         when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
-        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
         when(mDockManager.isDocked()).thenReturn(true);
         when(mDockManager.isHidden()).thenReturn(true);
         mMachine.requestState(INITIALIZED);
@@ -471,7 +480,9 @@
 
     @Test
     public void testTransitionToInitialized_carModeIsEnabled() {
-        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+        Configuration configuration = configWithCarNightUiMode();
+
+        mMachine.onConfigurationChanged(configuration);
         mMachine.requestState(INITIALIZED);
 
         verify(mPartMock).transitionTo(UNINITIALIZED, INITIALIZED);
@@ -481,7 +492,9 @@
 
     @Test
     public void testTransitionToFinish_carModeIsEnabled() {
-        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+        Configuration configuration = configWithCarNightUiMode();
+
+        mMachine.onConfigurationChanged(configuration);
         mMachine.requestState(INITIALIZED);
         mMachine.requestState(FINISH);
 
@@ -490,7 +503,9 @@
 
     @Test
     public void testDozeToDozeSuspendTriggers_carModeIsEnabled() {
-        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+        Configuration configuration = configWithCarNightUiMode();
+
+        mMachine.onConfigurationChanged(configuration);
         mMachine.requestState(INITIALIZED);
         mMachine.requestState(DOZE);
 
@@ -499,7 +514,9 @@
 
     @Test
     public void testDozeAoDToDozeSuspendTriggers_carModeIsEnabled() {
-        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+        Configuration configuration = configWithCarNightUiMode();
+
+        mMachine.onConfigurationChanged(configuration);
         mMachine.requestState(INITIALIZED);
         mMachine.requestState(DOZE_AOD);
 
@@ -508,7 +525,9 @@
 
     @Test
     public void testDozePulsingBrightDozeSuspendTriggers_carModeIsEnabled() {
-        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+        Configuration configuration = configWithCarNightUiMode();
+
+        mMachine.onConfigurationChanged(configuration);
         mMachine.requestState(INITIALIZED);
         mMachine.requestState(DOZE_PULSING_BRIGHT);
 
@@ -517,7 +536,9 @@
 
     @Test
     public void testDozeAodDockedDozeSuspendTriggers_carModeIsEnabled() {
-        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+        Configuration configuration = configWithCarNightUiMode();
+
+        mMachine.onConfigurationChanged(configuration);
         mMachine.requestState(INITIALIZED);
         mMachine.requestState(DOZE_AOD_DOCKED);
 
@@ -525,7 +546,35 @@
     }
 
     @Test
+    public void testOnConfigurationChanged_propagatesUiModeTypeToParts() {
+        Configuration newConfig = configWithCarNightUiMode();
+
+        mMachine.onConfigurationChanged(newConfig);
+
+        verify(mPartMock).onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+        verify(mAnotherPartMock).onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+    }
+
+    @Test
+    public void testOnConfigurationChanged_propagatesOnlyUiModeChangesToParts() {
+        Configuration newConfig = configWithCarNightUiMode();
+
+        mMachine.onConfigurationChanged(newConfig);
+        mMachine.onConfigurationChanged(newConfig);
+
+        verify(mPartMock, times(1)).onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+        verify(mAnotherPartMock, times(1)).onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+    }
+
+    @Test
     public void testDozeSuppressTriggers_screenState() {
         assertEquals(Display.STATE_OFF, DOZE_SUSPEND_TRIGGERS.screenState(null));
     }
+
+    @NonNull
+    private Configuration configWithCarNightUiMode() {
+        Configuration configuration = Configuration.EMPTY;
+        configuration.uiMode = UI_MODE_TYPE_CAR | UI_MODE_NIGHT_YES;
+        return configuration;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 9ffc5a5..c40c187 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -423,7 +423,7 @@
 
     @Test
     public void testGesturesAllInitiallyRespectSettings() {
-        DozeSensors dozeSensors = new DozeSensors(getContext(), mSensorManager, mDozeParameters,
+        DozeSensors dozeSensors = new DozeSensors(mSensorManager, mDozeParameters,
                 mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
                 mProximitySensor, mFakeSettings, mAuthController,
                 mDevicePostureController);
@@ -435,7 +435,7 @@
 
     private class TestableDozeSensors extends DozeSensors {
         TestableDozeSensors() {
-            super(getContext(), mSensorManager, mDozeParameters,
+            super(mSensorManager, mDozeParameters,
                     mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
                     mProximitySensor, mFakeSettings, mAuthController,
                     mDevicePostureController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java
index 0f29dcd..32b9945 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java
@@ -10,14 +10,14 @@
  * 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 andatest
+ * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 package com.android.systemui.doze;
 
-import static android.app.UiModeManager.ACTION_ENTER_CAR_MODE;
-import static android.app.UiModeManager.ACTION_EXIT_CAR_MODE;
+import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
+import static android.content.res.Configuration.UI_MODE_TYPE_NORMAL;
 
 import static com.android.systemui.doze.DozeMachine.State.DOZE;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
@@ -26,17 +26,16 @@
 import static com.android.systemui.doze.DozeMachine.State.INITIALIZED;
 import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED;
 
-import static org.hamcrest.Matchers.containsInAnyOrder;
-import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.UiModeManager;
-import android.content.BroadcastReceiver;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Configuration;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.testing.AndroidTestingRunner;
 import android.testing.UiThreadTest;
@@ -44,13 +43,13 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.AdditionalMatchers;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
@@ -71,10 +70,6 @@
     @Mock
     private AmbientDisplayConfiguration mConfig;
     @Mock
-    private BroadcastDispatcher mBroadcastDispatcher;
-    @Mock
-    private UiModeManager mUiModeManager;
-    @Mock
     private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
     @Mock
     private BiometricUnlockController mBiometricUnlockController;
@@ -83,13 +78,6 @@
     private DozeMachine mDozeMachine;
 
     @Captor
-    private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
-    @Captor
-    private ArgumentCaptor<IntentFilter> mIntentFilterCaptor;
-    private BroadcastReceiver mBroadcastReceiver;
-    private IntentFilter mIntentFilter;
-
-    @Captor
     private ArgumentCaptor<DozeHost.Callback> mDozeHostCaptor;
     private DozeHost.Callback mDozeHostCallback;
 
@@ -106,8 +94,6 @@
                 mDozeHost,
                 mConfig,
                 mDozeLog,
-                mBroadcastDispatcher,
-                mUiModeManager,
                 mBiometricUnlockControllerLazy);
 
         mDozeSuppressor.setDozeMachine(mDozeMachine);
@@ -122,36 +108,35 @@
     public void testRegistersListenersOnInitialized_unregisteredOnFinish() {
         // check that receivers and callbacks registered
         mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
-        captureBroadcastReceiver();
         captureDozeHostCallback();
 
         // check that receivers and callbacks are unregistered
         mDozeSuppressor.transitionTo(INITIALIZED, FINISH);
-        verify(mBroadcastDispatcher).unregisterReceiver(mBroadcastReceiver);
         verify(mDozeHost).removeCallback(mDozeHostCallback);
     }
 
     @Test
     public void testSuspendTriggersDoze_carMode() {
         // GIVEN car mode
-        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
 
         // WHEN dozing begins
         mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
 
         // THEN doze continues with all doze triggers disabled.
-        verify(mDozeMachine).requestState(DOZE_SUSPEND_TRIGGERS);
+        verify(mDozeMachine, atLeastOnce()).requestState(DOZE_SUSPEND_TRIGGERS);
+        verify(mDozeMachine, never())
+                .requestState(AdditionalMatchers.not(eq(DOZE_SUSPEND_TRIGGERS)));
     }
 
     @Test
     public void testSuspendTriggersDoze_enterCarMode() {
         // GIVEN currently dozing
         mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
-        captureBroadcastReceiver();
         mDozeSuppressor.transitionTo(INITIALIZED, DOZE);
 
         // WHEN car mode entered
-        mBroadcastReceiver.onReceive(null, new Intent(ACTION_ENTER_CAR_MODE));
+        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
 
         // THEN doze continues with all doze triggers disabled.
         verify(mDozeMachine).requestState(DOZE_SUSPEND_TRIGGERS);
@@ -160,13 +145,13 @@
     @Test
     public void testDozeResume_exitCarMode() {
         // GIVEN currently suspended, with AOD not enabled
+        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
         when(mConfig.alwaysOnEnabled(anyInt())).thenReturn(false);
         mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
-        captureBroadcastReceiver();
         mDozeSuppressor.transitionTo(INITIALIZED, DOZE_SUSPEND_TRIGGERS);
 
         // WHEN exiting car mode
-        mBroadcastReceiver.onReceive(null, new Intent(ACTION_EXIT_CAR_MODE));
+        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_NORMAL);
 
         // THEN doze is resumed
         verify(mDozeMachine).requestState(DOZE);
@@ -175,19 +160,53 @@
     @Test
     public void testDozeAoDResume_exitCarMode() {
         // GIVEN currently suspended, with AOD not enabled
+        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
         when(mConfig.alwaysOnEnabled(anyInt())).thenReturn(true);
         mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
-        captureBroadcastReceiver();
         mDozeSuppressor.transitionTo(INITIALIZED, DOZE_SUSPEND_TRIGGERS);
 
         // WHEN exiting car mode
-        mBroadcastReceiver.onReceive(null, new Intent(ACTION_EXIT_CAR_MODE));
+        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_NORMAL);
 
         // THEN doze AOD is resumed
         verify(mDozeMachine).requestState(DOZE_AOD);
     }
 
     @Test
+    public void testUiModeDoesNotChange_noStateTransition() {
+        mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
+        clearInvocations(mDozeMachine);
+
+        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+
+        verify(mDozeMachine, times(1)).requestState(DOZE_SUSPEND_TRIGGERS);
+        verify(mDozeMachine, never())
+                .requestState(AdditionalMatchers.not(eq(DOZE_SUSPEND_TRIGGERS)));
+    }
+
+    @Test
+    public void testUiModeTypeChange_whenDozeMachineIsNotReady_doesNotDoAnything() {
+        when(mDozeMachine.isUninitializedOrFinished()).thenReturn(true);
+
+        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+
+        verify(mDozeMachine, never()).requestState(any());
+    }
+
+    @Test
+    public void testUiModeTypeChange_CarModeEnabledAndDozeMachineNotReady_suspendsTriggersAfter() {
+        when(mDozeMachine.isUninitializedOrFinished()).thenReturn(true);
+        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+        verify(mDozeMachine, never()).requestState(any());
+
+        mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
+
+        verify(mDozeMachine, times(1)).requestState(DOZE_SUSPEND_TRIGGERS);
+    }
+
+    @Test
     public void testEndDoze_unprovisioned() {
         // GIVEN device unprovisioned
         when(mDozeHost.isProvisioned()).thenReturn(false);
@@ -276,14 +295,4 @@
         verify(mDozeHost).addCallback(mDozeHostCaptor.capture());
         mDozeHostCallback = mDozeHostCaptor.getValue();
     }
-
-    private void captureBroadcastReceiver() {
-        verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiverCaptor.capture(),
-                mIntentFilterCaptor.capture());
-        mBroadcastReceiver = mBroadcastReceiverCaptor.getValue();
-        mIntentFilter = mIntentFilterCaptor.getValue();
-        assertEquals(2, mIntentFilter.countActions());
-        org.hamcrest.MatcherAssert.assertThat(() -> mIntentFilter.actionsIterator(),
-                containsInAnyOrder(ACTION_ENTER_CAR_MODE, ACTION_EXIT_CAR_MODE));
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 781dc15..6091d3a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -23,10 +23,10 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -88,6 +88,8 @@
     @Mock
     private ProximityCheck mProximityCheck;
     @Mock
+    private DozeLog mDozeLog;
+    @Mock
     private AuthController mAuthController;
     @Mock
     private UiEventLogger mUiEventLogger;
@@ -127,7 +129,7 @@
 
         mTriggers = new DozeTriggers(mContext, mHost, config, dozeParameters,
                 asyncSensorManager, wakeLock, mDockManager, mProximitySensor,
-                mProximityCheck, mock(DozeLog.class), mBroadcastDispatcher, new FakeSettings(),
+                mProximityCheck, mDozeLog, mBroadcastDispatcher, new FakeSettings(),
                 mAuthController, mUiEventLogger, mSessionTracker, mKeyguardStateController,
                 mDevicePostureController);
         mTriggers.setDozeMachine(mMachine);
@@ -342,6 +344,16 @@
         verify(mProximityCheck).destroy();
     }
 
+    @Test
+    public void testIsExecutingTransition_dropPulse() {
+        when(mHost.isPulsePending()).thenReturn(false);
+        when(mMachine.isExecutingTransition()).thenReturn(true);
+
+        mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_LONG_PRESS, 100, 100, null);
+
+        verify(mDozeLog).tracePulseDropped(anyString(), eq(null));
+    }
+
     private void waitForSensorManager() {
         mExecutor.runAllReady();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java
index 522b5b5..50f27ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java
@@ -33,7 +33,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.MediaCarouselController;
+import com.android.systemui.media.controls.ui.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/dump/DumpHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
index fc67201..65ae90b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
@@ -17,11 +17,19 @@
 package com.android.systemui.dump
 
 import androidx.test.filters.SmallTest
+import com.android.systemui.CoreStartable
 import com.android.systemui.Dumpable
+import com.android.systemui.ProtoDumpable
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.google.common.truth.Truth.assertThat
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.io.StringWriter
+import javax.inject.Provider
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mock
@@ -29,7 +37,6 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
-import java.io.PrintWriter
 
 @SmallTest
 class DumpHandlerTest : SysuiTestCase() {
@@ -43,6 +50,8 @@
 
     @Mock
     private lateinit var pw: PrintWriter
+    @Mock
+    private lateinit var fd: FileDescriptor
 
     @Mock
     private lateinit var dumpable1: Dumpable
@@ -52,6 +61,11 @@
     private lateinit var dumpable3: Dumpable
 
     @Mock
+    private lateinit var protoDumpable1: ProtoDumpable
+    @Mock
+    private lateinit var protoDumpable2: ProtoDumpable
+
+    @Mock
     private lateinit var buffer1: LogBuffer
     @Mock
     private lateinit var buffer2: LogBuffer
@@ -66,7 +80,9 @@
             mContext,
             dumpManager,
             logBufferEulogizer,
-            mutableMapOf(),
+            mutableMapOf(
+                EmptyCoreStartable::class.java to Provider { EmptyCoreStartable() }
+            ),
             exceptionHandlerManager
         )
     }
@@ -82,7 +98,7 @@
 
         // WHEN some of them are dumped explicitly
         val args = arrayOf("dumpable1", "dumpable3", "buffer2")
-        dumpHandler.dump(pw, args)
+        dumpHandler.dump(fd, pw, args)
 
         // THEN only the requested ones have their dump() method called
         verify(dumpable1).dump(pw, args)
@@ -101,7 +117,7 @@
 
         // WHEN that module is dumped
         val args = arrayOf("dumpable1")
-        dumpHandler.dump(pw, args)
+        dumpHandler.dump(fd, pw, args)
 
         // THEN its dump() method is called
         verify(dumpable1).dump(pw, args)
@@ -118,7 +134,7 @@
 
         // WHEN a critical dump is requested
         val args = arrayOf("--dump-priority", "CRITICAL")
-        dumpHandler.dump(pw, args)
+        dumpHandler.dump(fd, pw, args)
 
         // THEN all modules are dumped (but no buffers)
         verify(dumpable1).dump(pw, args)
@@ -139,7 +155,7 @@
 
         // WHEN a normal dump is requested
         val args = arrayOf("--dump-priority", "NORMAL")
-        dumpHandler.dump(pw, args)
+        dumpHandler.dump(fd, pw, args)
 
         // THEN all buffers are dumped (but no modules)
         verify(dumpable1, never()).dump(
@@ -154,4 +170,44 @@
         verify(buffer1).dump(pw, 0)
         verify(buffer2).dump(pw, 0)
     }
-}
\ No newline at end of file
+
+    @Test
+    fun testConfigDump() {
+        // GIVEN a StringPrintWriter
+        val stringWriter = StringWriter()
+        val spw = PrintWriter(stringWriter)
+
+        // When a config dump is requested
+        dumpHandler.dump(fd, spw, arrayOf("config"))
+
+        assertThat(stringWriter.toString()).contains(EmptyCoreStartable::class.java.simpleName)
+    }
+
+    @Test
+    fun testDumpAllProtoDumpables() {
+        dumpManager.registerDumpable("protoDumpable1", protoDumpable1)
+        dumpManager.registerDumpable("protoDumpable2", protoDumpable2)
+
+        val args = arrayOf(DumpHandler.PROTO)
+        dumpHandler.dump(fd, pw, args)
+
+        verify(protoDumpable1).dumpProto(any(), eq(args))
+        verify(protoDumpable2).dumpProto(any(), eq(args))
+    }
+
+    @Test
+    fun testDumpSingleProtoDumpable() {
+        dumpManager.registerDumpable("protoDumpable1", protoDumpable1)
+        dumpManager.registerDumpable("protoDumpable2", protoDumpable2)
+
+        val args = arrayOf(DumpHandler.PROTO, "protoDumpable1")
+        dumpHandler.dump(fd, pw, args)
+
+        verify(protoDumpable1).dumpProto(any(), eq(args))
+        verify(protoDumpable2, never()).dumpProto(any(), any())
+    }
+
+    private class EmptyCoreStartable : CoreStartable {
+        override fun start() {}
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
index bd029a7..64547f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.dump
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogcatEchoTracker
 
 /**
  * Creates a LogBuffer that will echo everything to logcat, which is useful for debugging tests.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
index 4c61138..9628ee9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
@@ -51,14 +51,6 @@
     }
 
     @Test
-    fun noOpCommand() {
-        cmd.execute(pw, ArrayList())
-        Mockito.verify(pw, Mockito.atLeastOnce()).println()
-        Mockito.verify(featureFlags).isEnabled(flagA)
-        Mockito.verify(featureFlags).isEnabled(flagB)
-    }
-
-    @Test
     fun readFlagCommand() {
         cmd.execute(pw, listOf(flagA.id.toString()))
         Mockito.verify(featureFlags).isEnabled(flagA)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
deleted file mode 100644
index b5e9e8d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyObject;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Resources;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.View;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.AnimatableClockController;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.Utils;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.clocks.AnimatableClockView;
-import com.android.systemui.statusbar.policy.BatteryController;
-
-import org.junit.After;
-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;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-
-import java.util.concurrent.Executor;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class AnimatableClockControllerTest extends SysuiTestCase {
-    @Mock
-    private AnimatableClockView mClockView;
-    @Mock
-    private StatusBarStateController mStatusBarStateController;
-    @Mock
-    private BroadcastDispatcher mBroadcastDispatcher;
-    @Mock
-    private BatteryController mBatteryController;
-    @Mock
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock
-    private Resources mResources;
-    @Mock
-    private Executor mMainExecutor;
-    @Mock
-    private Executor mBgExecutor;
-    @Mock
-    private FeatureFlags mFeatureFlags;
-
-    private MockitoSession mStaticMockSession;
-    private AnimatableClockController mAnimatableClockController;
-
-    // Capture listeners so that they can be used to send events
-    @Captor private ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
-            ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
-    private View.OnAttachStateChangeListener mAttachListener;
-
-    @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor;
-    private StatusBarStateController.StateListener mStatusBarStateCallback;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mStaticMockSession = mockitoSession()
-                .mockStatic(Utils.class)
-                .strictness(Strictness.LENIENT) // it's ok if mocked classes aren't used
-                .startMocking();
-        when(Utils.getColorAttrDefaultColor(anyObject(), anyInt())).thenReturn(0);
-
-        mAnimatableClockController = new AnimatableClockController(
-                mClockView,
-                mStatusBarStateController,
-                mBroadcastDispatcher,
-                mBatteryController,
-                mKeyguardUpdateMonitor,
-                mResources,
-                mMainExecutor,
-                mBgExecutor,
-                mFeatureFlags
-        );
-        mAnimatableClockController.init();
-        captureAttachListener();
-    }
-
-    @After
-    public void tearDown() {
-        mStaticMockSession.finishMocking();
-    }
-
-    @Test
-    public void testOnAttachedUpdatesDozeStateToTrue() {
-        // GIVEN dozing
-        when(mStatusBarStateController.isDozing()).thenReturn(true);
-        when(mStatusBarStateController.getDozeAmount()).thenReturn(1f);
-
-        // WHEN the clock view gets attached
-        mAttachListener.onViewAttachedToWindow(mClockView);
-
-        // THEN the clock controller updated its dozing state to true
-        assertTrue(mAnimatableClockController.isDozing());
-    }
-
-    @Test
-    public void testOnAttachedUpdatesDozeStateToFalse() {
-        // GIVEN not dozing
-        when(mStatusBarStateController.isDozing()).thenReturn(false);
-        when(mStatusBarStateController.getDozeAmount()).thenReturn(0f);
-
-        // WHEN the clock view gets attached
-        mAttachListener.onViewAttachedToWindow(mClockView);
-
-        // THEN the clock controller updated its dozing state to false
-        assertFalse(mAnimatableClockController.isDozing());
-    }
-
-    private void captureAttachListener() {
-        verify(mClockView).addOnAttachStateChangeListener(mAttachCaptor.capture());
-        mAttachListener = mAttachCaptor.getValue();
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 39f3c96..4c986bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -19,6 +19,7 @@
 import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER;
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -229,6 +230,28 @@
     }
 
     @Test
+    public void testBouncerPrompt_nonStrongIdleTimeout() {
+        // GIVEN trust agents enabled and biometrics are enrolled
+        when(mUpdateMonitor.isTrustUsuallyManaged(anyInt())).thenReturn(true);
+        when(mUpdateMonitor.isUnlockingWithBiometricsPossible(anyInt())).thenReturn(true);
+
+        // WHEN the strong auth reason is STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
+        KeyguardUpdateMonitor.StrongAuthTracker strongAuthTracker =
+                mock(KeyguardUpdateMonitor.StrongAuthTracker.class);
+        when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(strongAuthTracker);
+        when(strongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
+        when(strongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout(
+                anyInt())).thenReturn(false);
+        when(strongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
+                STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT);
+
+        // THEN the bouncer prompt reason should return
+        // STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
+        assertEquals(KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT,
+                mViewMediator.mViewMediatorCallback.getBouncerPromptReason());
+    }
+
+    @Test
     public void testHideSurfaceBehindKeyguardMarksKeyguardNotGoingAway() {
         mViewMediator.hideSurfaceBehindKeyguard();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
new file mode 100644
index 0000000..1b34100
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -0,0 +1,259 @@
+/*
+ * 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.keyguard.data.repository
+
+import android.animation.AnimationHandler.AnimationFrameCallbackProvider
+import android.animation.ValueAnimator
+import android.util.Log
+import android.util.Log.TerribleFailure
+import android.util.Log.TerribleFailureHandler
+import android.view.Choreographer.FrameCallback
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.truth.Truth.assertThat
+import java.math.BigDecimal
+import java.math.RoundingMode
+import java.util.UUID
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.After
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardTransitionRepositoryTest : SysuiTestCase() {
+
+    private lateinit var underTest: KeyguardTransitionRepository
+    private lateinit var oldWtfHandler: TerribleFailureHandler
+    private lateinit var wtfHandler: WtfHandler
+
+    @Before
+    fun setUp() {
+        underTest = KeyguardTransitionRepository()
+        wtfHandler = WtfHandler()
+        oldWtfHandler = Log.setWtfHandler(wtfHandler)
+    }
+
+    @After
+    fun tearDown() {
+        oldWtfHandler?.let { Log.setWtfHandler(it) }
+    }
+
+    @Test
+    fun `startTransition runs animator to completion`() =
+        runBlocking(IMMEDIATE) {
+            val (animator, provider) = setupAnimator(this)
+
+            val steps = mutableListOf<TransitionStep>()
+            val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+
+            underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator))
+
+            val startTime = System.currentTimeMillis()
+            while (animator.isRunning()) {
+                yield()
+                if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) {
+                    fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION")
+                }
+            }
+
+            assertSteps(steps, listWithStep(BigDecimal(.1)))
+
+            job.cancel()
+            provider.stop()
+        }
+
+    @Test
+    fun `startTransition called during another transition fails`() {
+        underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, null))
+        underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, BOUNCER, null))
+
+        assertThat(wtfHandler.failed).isTrue()
+    }
+
+    @Test
+    fun `Null animator enables manual control with updateTransition`() =
+        runBlocking(IMMEDIATE) {
+            val steps = mutableListOf<TransitionStep>()
+            val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+
+            val uuid =
+                underTest.startTransition(
+                    TransitionInfo(
+                        ownerName = OWNER_NAME,
+                        from = AOD,
+                        to = LOCKSCREEN,
+                        animator = null,
+                    )
+                )
+
+            checkNotNull(uuid).let {
+                underTest.updateTransition(it, 0.5f, TransitionState.RUNNING)
+                underTest.updateTransition(it, 1f, TransitionState.FINISHED)
+            }
+
+            assertThat(steps.size).isEqualTo(3)
+            assertThat(steps[0])
+                .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+            assertThat(steps[1])
+                .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING))
+            assertThat(steps[2])
+                .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+            job.cancel()
+        }
+
+    @Test
+    fun `Attempt to  manually update transition with invalid UUID throws exception`() {
+        underTest.updateTransition(UUID.randomUUID(), 0f, TransitionState.RUNNING)
+        assertThat(wtfHandler.failed).isTrue()
+    }
+
+    @Test
+    fun `Attempt to manually update transition after FINISHED state throws exception`() {
+        val uuid =
+            underTest.startTransition(
+                TransitionInfo(
+                    ownerName = OWNER_NAME,
+                    from = AOD,
+                    to = LOCKSCREEN,
+                    animator = null,
+                )
+            )
+
+        checkNotNull(uuid).let {
+            underTest.updateTransition(it, 1f, TransitionState.FINISHED)
+            underTest.updateTransition(it, 0.5f, TransitionState.RUNNING)
+        }
+        assertThat(wtfHandler.failed).isTrue()
+    }
+
+    private fun listWithStep(step: BigDecimal): List<BigDecimal> {
+        val steps = mutableListOf<BigDecimal>()
+
+        var i = BigDecimal.ZERO
+        while (i.compareTo(BigDecimal.ONE) <= 0) {
+            steps.add(i)
+            i = (i + step).setScale(2, RoundingMode.HALF_UP)
+        }
+
+        return steps
+    }
+
+    private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) {
+        // + 2 accounts for start and finish of automated transition
+        assertThat(steps.size).isEqualTo(fractions.size + 2)
+
+        assertThat(steps[0]).isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+        fractions.forEachIndexed { index, fraction ->
+            assertThat(steps[index + 1])
+                .isEqualTo(
+                    TransitionStep(AOD, LOCKSCREEN, fraction.toFloat(), TransitionState.RUNNING)
+                )
+        }
+        assertThat(steps[steps.size - 1])
+            .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+
+        assertThat(wtfHandler.failed).isFalse()
+    }
+
+    private fun setupAnimator(
+        scope: CoroutineScope
+    ): Pair<ValueAnimator, TestFrameCallbackProvider> {
+        val animator =
+            ValueAnimator().apply {
+                setInterpolator(Interpolators.LINEAR)
+                setDuration(ANIMATION_DURATION)
+            }
+
+        val provider = TestFrameCallbackProvider(animator, scope)
+        provider.start()
+
+        return Pair(animator, provider)
+    }
+
+    /** Gives direct control over ValueAnimator. See [AnimationHandler] */
+    private class TestFrameCallbackProvider(
+        private val animator: ValueAnimator,
+        private val scope: CoroutineScope,
+    ) : AnimationFrameCallbackProvider {
+
+        private var frameCount = 1L
+        private var frames = MutableStateFlow(Pair<Long, FrameCallback?>(0L, null))
+        private var job: Job? = null
+
+        fun start() {
+            animator.getAnimationHandler().setProvider(this)
+
+            job =
+                scope.launch {
+                    frames.collect {
+                        // Delay is required for AnimationHandler to properly register a callback
+                        delay(1)
+                        val (frameNumber, callback) = it
+                        callback?.doFrame(frameNumber)
+                    }
+                }
+        }
+
+        fun stop() {
+            job?.cancel()
+            animator.getAnimationHandler().setProvider(null)
+        }
+
+        override fun postFrameCallback(cb: FrameCallback) {
+            frames.value = Pair(++frameCount, cb)
+        }
+        override fun postCommitCallback(runnable: Runnable) {}
+        override fun getFrameTime() = frameCount
+        override fun getFrameDelay() = 1L
+        override fun setFrameDelay(delay: Long) {}
+    }
+
+    private class WtfHandler : TerribleFailureHandler {
+        var failed = false
+        override fun onTerribleFailure(tag: String, what: TerribleFailure, system: Boolean) {
+            failed = true
+        }
+    }
+
+    companion object {
+        private const val MAX_TEST_DURATION = 100L
+        private const val ANIMATION_DURATION = 10L
+        private const val OWNER_NAME = "Test"
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
deleted file mode 100644
index f34c2ac..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
+++ /dev/null
@@ -1,426 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media
-
-import android.app.PendingIntent
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import androidx.test.filters.SmallTest
-import com.android.internal.logging.InstanceId
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.MediaCarouselController.Companion.ANIMATION_BASE_DURATION
-import com.android.systemui.media.MediaCarouselController.Companion.DURATION
-import com.android.systemui.media.MediaCarouselController.Companion.PAGINATION_DELAY
-import com.android.systemui.media.MediaCarouselController.Companion.TRANSFORM_BEZIER
-import com.android.systemui.media.MediaHierarchyManager.Companion.LOCATION_QS
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.time.FakeSystemClock
-import javax.inject.Provider
-import junit.framework.Assert.assertEquals
-import junit.framework.Assert.assertTrue
-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.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
-
-private val DATA = MediaTestUtils.emptyMediaData
-
-private val SMARTSPACE_KEY = "smartspace"
-
-@SmallTest
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidTestingRunner::class)
-class MediaCarouselControllerTest : SysuiTestCase() {
-
-    @Mock lateinit var mediaControlPanelFactory: Provider<MediaControlPanel>
-    @Mock lateinit var panel: MediaControlPanel
-    @Mock lateinit var visualStabilityProvider: VisualStabilityProvider
-    @Mock lateinit var mediaHostStatesManager: MediaHostStatesManager
-    @Mock lateinit var mediaHostState: MediaHostState
-    @Mock lateinit var activityStarter: ActivityStarter
-    @Mock @Main private lateinit var executor: DelayableExecutor
-    @Mock lateinit var mediaDataManager: MediaDataManager
-    @Mock lateinit var configurationController: ConfigurationController
-    @Mock lateinit var falsingCollector: FalsingCollector
-    @Mock lateinit var falsingManager: FalsingManager
-    @Mock lateinit var dumpManager: DumpManager
-    @Mock lateinit var logger: MediaUiEventLogger
-    @Mock lateinit var debugLogger: MediaCarouselControllerLogger
-    @Mock lateinit var mediaPlayer: MediaControlPanel
-    @Mock lateinit var mediaViewController: MediaViewController
-    @Mock lateinit var smartspaceMediaData: SmartspaceMediaData
-    @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
-    @Captor lateinit var visualStabilityCallback: ArgumentCaptor<OnReorderingAllowedListener>
-
-    private val clock = FakeSystemClock()
-    private lateinit var mediaCarouselController: MediaCarouselController
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        mediaCarouselController = MediaCarouselController(
-            context,
-            mediaControlPanelFactory,
-            visualStabilityProvider,
-            mediaHostStatesManager,
-            activityStarter,
-            clock,
-            executor,
-            mediaDataManager,
-            configurationController,
-            falsingCollector,
-            falsingManager,
-            dumpManager,
-            logger,
-            debugLogger
-        )
-        verify(mediaDataManager).addListener(capture(listener))
-        verify(visualStabilityProvider)
-            .addPersistentReorderingAllowedListener(capture(visualStabilityCallback))
-        whenever(mediaControlPanelFactory.get()).thenReturn(mediaPlayer)
-        whenever(mediaPlayer.mediaViewController).thenReturn(mediaViewController)
-        whenever(mediaDataManager.smartspaceMediaData).thenReturn(smartspaceMediaData)
-        MediaPlayerData.clear()
-    }
-
-    @Test
-    fun testPlayerOrdering() {
-        // Test values: key, data, last active time
-        val playingLocal = Triple("playing local",
-            DATA.copy(active = true, isPlaying = true,
-                    playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false),
-            4500L)
-
-        val playingCast = Triple("playing cast",
-            DATA.copy(active = true, isPlaying = true,
-                    playbackLocation = MediaData.PLAYBACK_CAST_LOCAL, resumption = false),
-            5000L)
-
-        val pausedLocal = Triple("paused local",
-            DATA.copy(active = true, isPlaying = false,
-                    playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false),
-            1000L)
-
-        val pausedCast = Triple("paused cast",
-            DATA.copy(active = true, isPlaying = false,
-                    playbackLocation = MediaData.PLAYBACK_CAST_LOCAL, resumption = false),
-            2000L)
-
-        val playingRcn = Triple("playing RCN",
-            DATA.copy(active = true, isPlaying = true,
-                    playbackLocation = MediaData.PLAYBACK_CAST_REMOTE, resumption = false),
-            5000L)
-
-        val pausedRcn = Triple("paused RCN",
-                DATA.copy(active = true, isPlaying = false,
-                        playbackLocation = MediaData.PLAYBACK_CAST_REMOTE, resumption = false),
-                5000L)
-
-        val active = Triple("active",
-            DATA.copy(active = true, isPlaying = false,
-                playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true),
-            250L)
-
-        val resume1 = Triple("resume 1",
-            DATA.copy(active = false, isPlaying = false,
-                    playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true),
-            500L)
-
-        val resume2 = Triple("resume 2",
-            DATA.copy(active = false, isPlaying = false,
-                playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true),
-            1000L)
-
-        val activeMoreRecent = Triple("active more recent",
-            DATA.copy(active = false, isPlaying = false,
-                playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true, lastActive = 2L),
-            1000L)
-
-        val activeLessRecent = Triple("active less recent",
-            DATA.copy(active = false, isPlaying = false,
-                playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true, lastActive = 1L),
-            1000L)
-        // Expected ordering for media players:
-        // Actively playing local sessions
-        // Actively playing cast sessions
-        // Paused local and cast sessions, by last active
-        // RCNs
-        // Resume controls, by last active
-
-        val expected = listOf(playingLocal, playingCast, pausedCast, pausedLocal, playingRcn,
-                pausedRcn, active, resume2, resume1)
-
-        expected.forEach {
-            clock.setCurrentTimeMillis(it.third)
-            MediaPlayerData.addMediaPlayer(it.first, it.second.copy(notificationKey = it.first),
-                panel, clock, isSsReactivated = false)
-        }
-
-        for ((index, key) in MediaPlayerData.playerKeys().withIndex()) {
-            assertEquals(expected.get(index).first, key.data.notificationKey)
-        }
-    }
-
-    @Test
-    fun testOrderWithSmartspace_prioritized() {
-        testPlayerOrdering()
-
-        // If smartspace is prioritized
-        MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
-            true, clock)
-
-        // Then it should be shown immediately after any actively playing controls
-        assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
-    }
-
-    @Test
-    fun testOrderWithSmartspace_notPrioritized() {
-        testPlayerOrdering()
-
-        // If smartspace is not prioritized
-        MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
-            false, clock)
-
-        // Then it should be shown at the end of the carousel's active entries
-        val idx = MediaPlayerData.playerKeys().count { it.data.active } - 1
-        assertTrue(MediaPlayerData.playerKeys().elementAt(idx).isSsMediaRec)
-    }
-
-    @Test
-    fun testSwipeDismiss_logged() {
-        mediaCarouselController.mediaCarouselScrollHandler.dismissCallback.invoke()
-
-        verify(logger).logSwipeDismiss()
-    }
-
-    @Test
-    fun testSettingsButton_logged() {
-        mediaCarouselController.settingsButton.callOnClick()
-
-        verify(logger).logCarouselSettings()
-    }
-
-    @Test
-    fun testLocationChangeQs_logged() {
-        mediaCarouselController.onDesiredLocationChanged(
-            MediaHierarchyManager.LOCATION_QS,
-            mediaHostState,
-            animate = false)
-        verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QS)
-    }
-
-    @Test
-    fun testLocationChangeQqs_logged() {
-        mediaCarouselController.onDesiredLocationChanged(
-            MediaHierarchyManager.LOCATION_QQS,
-            mediaHostState,
-            animate = false)
-        verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS)
-    }
-
-    @Test
-    fun testLocationChangeLockscreen_logged() {
-        mediaCarouselController.onDesiredLocationChanged(
-            MediaHierarchyManager.LOCATION_LOCKSCREEN,
-            mediaHostState,
-            animate = false)
-        verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN)
-    }
-
-    @Test
-    fun testLocationChangeDream_logged() {
-        mediaCarouselController.onDesiredLocationChanged(
-            MediaHierarchyManager.LOCATION_DREAM_OVERLAY,
-            mediaHostState,
-            animate = false)
-        verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY)
-    }
-
-    @Test
-    fun testRecommendationRemoved_logged() {
-        val packageName = "smartspace package"
-        val instanceId = InstanceId.fakeInstanceId(123)
-
-        val smartspaceData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
-            packageName = packageName,
-            instanceId = instanceId
-        )
-        MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, smartspaceData, panel, true, clock)
-        mediaCarouselController.removePlayer(SMARTSPACE_KEY)
-
-        verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!))
-    }
-
-    fun testMediaLoaded_ScrollToActivePlayer() {
-        listener.value.onMediaDataLoaded("playing local",
-                null,
-                DATA.copy(active = true, isPlaying = true,
-                        playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false)
-        )
-        listener.value.onMediaDataLoaded("paused local",
-                null,
-                DATA.copy(active = true, isPlaying = false,
-                        playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false))
-        // adding a media recommendation card.
-        MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
-                false, clock)
-        mediaCarouselController.shouldScrollToActivePlayer = true
-        // switching between media players.
-        listener.value.onMediaDataLoaded("playing local",
-        "playing local",
-                DATA.copy(active = true, isPlaying = false,
-                        playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true)
-        )
-        listener.value.onMediaDataLoaded("paused local",
-                "paused local",
-                DATA.copy(active = true, isPlaying = true,
-                        playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false))
-
-        assertEquals(
-                MediaPlayerData.getMediaPlayerIndex("paused local"),
-                mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
-        )
-    }
-
-    @Test
-    fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() {
-        MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
-                false, clock)
-        listener.value.onMediaDataLoaded("playing local",
-                null,
-                DATA.copy(active = true, isPlaying = true,
-                        playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false)
-        )
-
-        var playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local")
-        assertEquals(
-                playerIndex,
-                mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
-        )
-        assertEquals(playerIndex, 0)
-
-        // Replaying the same media player one more time.
-        // And check that the card stays in its position.
-        listener.value.onMediaDataLoaded("playing local",
-                null,
-                DATA.copy(active = true, isPlaying = true,
-                        playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false)
-        )
-        playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local")
-        assertEquals(playerIndex, 0)
-    }
-
-    @Test
-    fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() {
-        var result = false
-        mediaCarouselController.updateHostVisibility = { result = true }
-
-        whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(true)
-        listener.value.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY, false)
-
-        assertEquals(true, result)
-    }
-
-    @Test
-    fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() {
-        var result = false
-        mediaCarouselController.updateHostVisibility = { result = true }
-
-        whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(false)
-        listener.value.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY, false)
-        assertEquals(false, result)
-
-        visualStabilityCallback.value.onReorderingAllowed()
-        assertEquals(true, result)
-    }
-
-    @Test
-    fun testGetCurrentVisibleMediaContentIntent() {
-        val clickIntent1 = mock(PendingIntent::class.java)
-        val player1 = Triple("player1",
-                DATA.copy(clickIntent = clickIntent1),
-                1000L)
-        clock.setCurrentTimeMillis(player1.third)
-        MediaPlayerData.addMediaPlayer(player1.first,
-                player1.second.copy(notificationKey = player1.first),
-                panel, clock, isSsReactivated = false)
-
-        assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent1)
-
-        val clickIntent2 = mock(PendingIntent::class.java)
-        val player2 = Triple("player2",
-                DATA.copy(clickIntent = clickIntent2),
-                2000L)
-        clock.setCurrentTimeMillis(player2.third)
-        MediaPlayerData.addMediaPlayer(player2.first,
-                player2.second.copy(notificationKey = player2.first),
-                panel, clock, isSsReactivated = false)
-
-        // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is
-        // added to the front because it was active more recently.
-        assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
-
-        val clickIntent3 = mock(PendingIntent::class.java)
-        val player3 = Triple("player3",
-                DATA.copy(clickIntent = clickIntent3),
-                500L)
-        clock.setCurrentTimeMillis(player3.third)
-        MediaPlayerData.addMediaPlayer(player3.first,
-                player3.second.copy(notificationKey = player3.first),
-                panel, clock, isSsReactivated = false)
-
-        // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is
-        // added to the end because it was active less recently.
-        assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
-    }
-
-    @Test
-    fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() {
-        val delta = 0.0001F
-        val paginationSquishMiddle = TRANSFORM_BEZIER.getInterpolation(
-                (PAGINATION_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION)
-        val paginationSquishEnd = TRANSFORM_BEZIER.getInterpolation(
-                (PAGINATION_DELAY + DURATION) / ANIMATION_BASE_DURATION)
-        whenever(mediaHostStatesManager.mediaHostStates)
-            .thenReturn(mutableMapOf(LOCATION_QS to mediaHostState))
-        whenever(mediaHostState.visible).thenReturn(true)
-        mediaCarouselController.currentEndLocation = LOCATION_QS
-        whenever(mediaHostState.squishFraction).thenReturn(paginationSquishMiddle)
-        mediaCarouselController.updatePageIndicatorAlpha()
-        assertEquals(mediaCarouselController.pageIndicator.alpha, 0.5F, delta)
-
-        whenever(mediaHostState.squishFraction).thenReturn(paginationSquishEnd)
-        mediaCarouselController.updatePageIndicatorAlpha()
-        assertEquals(mediaCarouselController.pageIndicator.alpha, 1.0F, delta)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
deleted file mode 100644
index 6e38d264..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2020 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.media
-
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.mock
-import org.mockito.junit.MockitoJUnit
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-public class MediaPlayerDataTest : SysuiTestCase() {
-
-    @Mock
-    private lateinit var playerIsPlaying: MediaControlPanel
-    private var systemClock: FakeSystemClock = FakeSystemClock()
-
-    @JvmField
-    @Rule
-    val mockito = MockitoJUnit.rule()
-
-    companion object {
-        val LOCAL = MediaData.PLAYBACK_LOCAL
-        val REMOTE = MediaData.PLAYBACK_CAST_LOCAL
-        val RESUMPTION = true
-        val PLAYING = true
-        val UNDETERMINED = null
-    }
-
-    @Before
-    fun setup() {
-        MediaPlayerData.clear()
-    }
-
-    @Test
-    fun addPlayingThenRemote() {
-        val dataIsPlaying = createMediaData("app1", PLAYING, LOCAL, !RESUMPTION)
-
-        val playerIsRemote = mock(MediaControlPanel::class.java)
-        val dataIsRemote = createMediaData("app2", PLAYING, REMOTE, !RESUMPTION)
-
-        MediaPlayerData.addMediaPlayer("2", dataIsRemote, playerIsRemote, systemClock,
-                isSsReactivated = false)
-        MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying, systemClock,
-                isSsReactivated = false)
-
-        val players = MediaPlayerData.players()
-        assertThat(players).hasSize(2)
-        assertThat(players).containsExactly(playerIsPlaying, playerIsRemote).inOrder()
-    }
-
-    @Test
-    fun switchPlayersPlaying() {
-        val playerIsPlaying1 = mock(MediaControlPanel::class.java)
-        var dataIsPlaying1 = createMediaData("app1", PLAYING, LOCAL, !RESUMPTION)
-
-        val playerIsPlaying2 = mock(MediaControlPanel::class.java)
-        var dataIsPlaying2 = createMediaData("app2", !PLAYING, LOCAL, !RESUMPTION)
-
-        MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1, systemClock,
-                isSsReactivated = false)
-        systemClock.advanceTime(1)
-        MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2, systemClock,
-                isSsReactivated = false)
-        systemClock.advanceTime(1)
-
-        dataIsPlaying1 = createMediaData("app1", !PLAYING, LOCAL, !RESUMPTION)
-        dataIsPlaying2 = createMediaData("app2", PLAYING, LOCAL, !RESUMPTION)
-
-        MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1, systemClock,
-                isSsReactivated = false)
-        systemClock.advanceTime(1)
-
-        MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2, systemClock,
-                isSsReactivated = false)
-        systemClock.advanceTime(1)
-
-        val players = MediaPlayerData.players()
-        assertThat(players).hasSize(2)
-        assertThat(players).containsExactly(playerIsPlaying2, playerIsPlaying1).inOrder()
-    }
-
-    @Test
-    fun fullOrderTest() {
-        val dataIsPlaying = createMediaData("app1", PLAYING, LOCAL, !RESUMPTION)
-
-        val playerIsPlayingAndRemote = mock(MediaControlPanel::class.java)
-        val dataIsPlayingAndRemote = createMediaData("app2", PLAYING, REMOTE, !RESUMPTION)
-
-        val playerIsStoppedAndLocal = mock(MediaControlPanel::class.java)
-        val dataIsStoppedAndLocal = createMediaData("app3", !PLAYING, LOCAL, !RESUMPTION)
-
-        val playerIsStoppedAndRemote = mock(MediaControlPanel::class.java)
-        val dataIsStoppedAndRemote = createMediaData("app4", !PLAYING, REMOTE, !RESUMPTION)
-
-        val playerCanResume = mock(MediaControlPanel::class.java)
-        val dataCanResume = createMediaData("app5", !PLAYING, LOCAL, RESUMPTION)
-
-        val playerUndetermined = mock(MediaControlPanel::class.java)
-        val dataUndetermined = createMediaData("app6", UNDETERMINED, LOCAL, RESUMPTION)
-
-        MediaPlayerData.addMediaPlayer(
-                "3", dataIsStoppedAndLocal, playerIsStoppedAndLocal, systemClock,
-                isSsReactivated = false)
-        MediaPlayerData.addMediaPlayer(
-                "5", dataIsStoppedAndRemote, playerIsStoppedAndRemote, systemClock,
-                isSsReactivated = false)
-        MediaPlayerData.addMediaPlayer("4", dataCanResume, playerCanResume, systemClock,
-                isSsReactivated = false)
-        MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying, systemClock,
-                isSsReactivated = false)
-        MediaPlayerData.addMediaPlayer(
-                "2", dataIsPlayingAndRemote, playerIsPlayingAndRemote, systemClock,
-                isSsReactivated = false)
-        MediaPlayerData.addMediaPlayer("6", dataUndetermined, playerUndetermined, systemClock,
-                isSsReactivated = false)
-
-        val players = MediaPlayerData.players()
-        assertThat(players).hasSize(6)
-        assertThat(players).containsExactly(playerIsPlaying, playerIsPlayingAndRemote,
-            playerIsStoppedAndRemote, playerIsStoppedAndLocal, playerUndetermined,
-            playerCanResume).inOrder()
-    }
-
-    @Test
-    fun testMoveMediaKeysAround() {
-        val keyA = "a"
-        val keyB = "b"
-
-        val data = createMediaData("app1", PLAYING, LOCAL, !RESUMPTION)
-
-        assertThat(MediaPlayerData.players()).hasSize(0)
-
-        MediaPlayerData.addMediaPlayer(keyA, data, playerIsPlaying, systemClock,
-                isSsReactivated = false)
-        systemClock.advanceTime(1)
-
-        assertThat(MediaPlayerData.players()).hasSize(1)
-        MediaPlayerData.addMediaPlayer(keyB, data, playerIsPlaying, systemClock,
-                isSsReactivated = false)
-        systemClock.advanceTime(1)
-
-        assertThat(MediaPlayerData.players()).hasSize(2)
-
-        MediaPlayerData.moveIfExists(keyA, keyB)
-
-        assertThat(MediaPlayerData.players()).hasSize(1)
-
-        assertThat(MediaPlayerData.getMediaPlayer(keyA)).isNull()
-        assertThat(MediaPlayerData.getMediaPlayer(keyB)).isNotNull()
-    }
-
-    private fun createMediaData(
-        app: String,
-        isPlaying: Boolean?,
-        location: Int,
-        resumption: Boolean
-    ) = MediaTestUtils.emptyMediaData.copy(
-        app = app,
-        packageName = "package: $app",
-        playbackLocation = location,
-        resumption = resumption,
-        notificationKey = "key: $app",
-        isPlaying = isPlaying
-    )
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt
deleted file mode 100644
index 3d9ed5f..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.android.systemui.media
-
-import com.android.internal.logging.InstanceId
-
-class MediaTestUtils {
-    companion object {
-        val emptyMediaData = MediaData(
-            userId = 0,
-            initialized = true,
-            app = null,
-            appIcon = null,
-            artist = null,
-            song = null,
-            artwork = null,
-            actions = emptyList(),
-            actionsToShowInCompact = emptyList(),
-            packageName = "",
-            token = null,
-            clickIntent = null,
-            device = null,
-            active = true,
-            resumeAction = null,
-            isPlaying = false,
-            instanceId = InstanceId.fakeInstanceId(-1),
-            appUid = -1)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewHolderTest.kt
deleted file mode 100644
index ee32793..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewHolderTest.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.android.systemui.media
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.LayoutInflater
-import android.widget.FrameLayout
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-class MediaViewHolderTest : SysuiTestCase() {
-
-    @Test
-    fun create_succeeds() {
-        val inflater = LayoutInflater.from(context)
-        val parent = FrameLayout(context)
-
-        MediaViewHolder.create(inflater, parent)
-    }
-}
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
new file mode 100644
index 0000000..3437365
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/MediaTestUtils.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.media.controls
+
+import com.android.internal.logging.InstanceId
+import com.android.systemui.media.controls.models.player.MediaData
+
+class MediaTestUtils {
+    companion object {
+        val emptyMediaData =
+            MediaData(
+                userId = 0,
+                initialized = true,
+                app = null,
+                appIcon = null,
+                artist = null,
+                song = null,
+                artwork = null,
+                actions = emptyList(),
+                actionsToShowInCompact = emptyList(),
+                packageName = "",
+                token = null,
+                clickIntent = null,
+                device = null,
+                active = true,
+                resumeAction = null,
+                isPlaying = false,
+                instanceId = InstanceId.fakeInstanceId(-1),
+                appUid = -1
+            )
+    }
+}
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/models/player/MediaViewHolderTest.kt
new file mode 100644
index 0000000..c829d4c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/MediaViewHolderTest.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.media.controls.models.player
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class MediaViewHolderTest : SysuiTestCase() {
+
+    @Test
+    fun create_succeeds() {
+        val inflater = LayoutInflater.from(context)
+        val parent = FrameLayout(context)
+
+        MediaViewHolder.create(inflater, parent)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
similarity index 92%
rename from packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
index 9e9cda8..97b18e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.models.player
 
 import android.animation.Animator
 import android.animation.ObjectAnimator
@@ -26,6 +26,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.controls.ui.SquigglyProgress
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
@@ -33,8 +34,8 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -56,10 +57,14 @@
 
     @Before
     fun setUp() {
-        context.orCreateTestableResources
-            .addOverride(R.dimen.qs_media_enabled_seekbar_height, enabledHeight)
-        context.orCreateTestableResources
-            .addOverride(R.dimen.qs_media_disabled_seekbar_height, disabledHeight)
+        context.orCreateTestableResources.addOverride(
+            R.dimen.qs_media_enabled_seekbar_height,
+            enabledHeight
+        )
+        context.orCreateTestableResources.addOverride(
+            R.dimen.qs_media_disabled_seekbar_height,
+            disabledHeight
+        )
 
         seekBarView = SeekBar(context)
         seekBarView.progressDrawable = mockSquigglyProgress
@@ -69,11 +74,12 @@
         whenever(mockHolder.scrubbingElapsedTimeView).thenReturn(scrubbingElapsedTimeView)
         whenever(mockHolder.scrubbingTotalTimeView).thenReturn(scrubbingTotalTimeView)
 
-        observer = object : SeekBarObserver(mockHolder) {
-            override fun buildResetAnimator(targetTime: Int): Animator {
-                return mockSeekbarAnimator
+        observer =
+            object : SeekBarObserver(mockHolder) {
+                override fun buildResetAnimator(targetTime: Int): Animator {
+                    return mockSeekbarAnimator
+                }
             }
-        }
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
similarity index 81%
rename from packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
index 5973340..7cd8e74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.models.player
 
 import android.media.MediaMetadata
 import android.media.session.MediaController
@@ -57,17 +57,18 @@
 
     private lateinit var viewModel: SeekBarViewModel
     private lateinit var fakeExecutor: FakeExecutor
-    private val taskExecutor: TaskExecutor = object : TaskExecutor() {
-        override fun executeOnDiskIO(runnable: Runnable) {
-            runnable.run()
+    private val taskExecutor: TaskExecutor =
+        object : TaskExecutor() {
+            override fun executeOnDiskIO(runnable: Runnable) {
+                runnable.run()
+            }
+            override fun postToMainThread(runnable: Runnable) {
+                runnable.run()
+            }
+            override fun isMainThread(): Boolean {
+                return true
+            }
         }
-        override fun postToMainThread(runnable: Runnable) {
-            runnable.run()
-        }
-        override fun isMainThread(): Boolean {
-            return true
-        }
-    }
     @Mock private lateinit var mockController: MediaController
     @Mock private lateinit var mockTransport: MediaController.TransportControls
     @Mock private lateinit var falsingManager: FalsingManager
@@ -81,7 +82,7 @@
     fun setUp() {
         fakeExecutor = FakeExecutor(FakeSystemClock())
         viewModel = SeekBarViewModel(FakeRepeatableExecutor(fakeExecutor), falsingManager)
-        viewModel.logSeek = { }
+        viewModel.logSeek = {}
         whenever(mockController.sessionToken).thenReturn(token1)
         whenever(mockBar.context).thenReturn(context)
 
@@ -135,16 +136,18 @@
     fun updateDurationWithPlayback() {
         // GIVEN that the duration is contained within the metadata
         val duration = 12000L
-        val metadata = MediaMetadata.Builder().run {
-            putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
-            build()
-        }
+        val metadata =
+            MediaMetadata.Builder().run {
+                putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
+                build()
+            }
         whenever(mockController.getMetadata()).thenReturn(metadata)
         // AND a valid playback state (ie. media session is not destroyed)
-        val state = PlaybackState.Builder().run {
-            setState(PlaybackState.STATE_PLAYING, 200L, 1f)
-            build()
-        }
+        val state =
+            PlaybackState.Builder().run {
+                setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+                build()
+            }
         whenever(mockController.getPlaybackState()).thenReturn(state)
         // WHEN the controller is updated
         viewModel.updateController(mockController)
@@ -158,10 +161,11 @@
     fun updateDurationWithoutPlayback() {
         // GIVEN that the duration is contained within the metadata
         val duration = 12000L
-        val metadata = MediaMetadata.Builder().run {
-            putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
-            build()
-        }
+        val metadata =
+            MediaMetadata.Builder().run {
+                putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
+                build()
+            }
         whenever(mockController.getMetadata()).thenReturn(metadata)
         // WHEN the controller is updated
         viewModel.updateController(mockController)
@@ -174,16 +178,18 @@
     fun updateDurationNegative() {
         // GIVEN that the duration is negative
         val duration = -1L
-        val metadata = MediaMetadata.Builder().run {
-            putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
-            build()
-        }
+        val metadata =
+            MediaMetadata.Builder().run {
+                putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
+                build()
+            }
         whenever(mockController.getMetadata()).thenReturn(metadata)
         // AND a valid playback state (ie. media session is not destroyed)
-        val state = PlaybackState.Builder().run {
-            setState(PlaybackState.STATE_PLAYING, 200L, 1f)
-            build()
-        }
+        val state =
+            PlaybackState.Builder().run {
+                setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+                build()
+            }
         whenever(mockController.getPlaybackState()).thenReturn(state)
         // WHEN the controller is updated
         viewModel.updateController(mockController)
@@ -195,16 +201,18 @@
     fun updateDurationZero() {
         // GIVEN that the duration is zero
         val duration = 0L
-        val metadata = MediaMetadata.Builder().run {
-            putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
-            build()
-        }
+        val metadata =
+            MediaMetadata.Builder().run {
+                putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
+                build()
+            }
         whenever(mockController.getMetadata()).thenReturn(metadata)
         // AND a valid playback state (ie. media session is not destroyed)
-        val state = PlaybackState.Builder().run {
-            setState(PlaybackState.STATE_PLAYING, 200L, 1f)
-            build()
-        }
+        val state =
+            PlaybackState.Builder().run {
+                setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+                build()
+            }
         whenever(mockController.getPlaybackState()).thenReturn(state)
         // WHEN the controller is updated
         viewModel.updateController(mockController)
@@ -218,10 +226,11 @@
         // GIVEN that the metadata is null
         whenever(mockController.getMetadata()).thenReturn(null)
         // AND a valid playback state (ie. media session is not destroyed)
-        val state = PlaybackState.Builder().run {
-            setState(PlaybackState.STATE_PLAYING, 200L, 1f)
-            build()
-        }
+        val state =
+            PlaybackState.Builder().run {
+                setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+                build()
+            }
         whenever(mockController.getPlaybackState()).thenReturn(state)
         // WHEN the controller is updated
         viewModel.updateController(mockController)
@@ -233,10 +242,11 @@
     fun updateElapsedTime() {
         // GIVEN that the PlaybackState contains the current position
         val position = 200L
-        val state = PlaybackState.Builder().run {
-            setState(PlaybackState.STATE_PLAYING, position, 1f)
-            build()
-        }
+        val state =
+            PlaybackState.Builder().run {
+                setState(PlaybackState.STATE_PLAYING, position, 1f)
+                build()
+            }
         whenever(mockController.getPlaybackState()).thenReturn(state)
         // WHEN the controller is updated
         viewModel.updateController(mockController)
@@ -248,10 +258,11 @@
     @Ignore
     fun updateSeekAvailable() {
         // GIVEN that seek is included in actions
-        val state = PlaybackState.Builder().run {
-            setActions(PlaybackState.ACTION_SEEK_TO)
-            build()
-        }
+        val state =
+            PlaybackState.Builder().run {
+                setActions(PlaybackState.ACTION_SEEK_TO)
+                build()
+            }
         whenever(mockController.getPlaybackState()).thenReturn(state)
         // WHEN the controller is updated
         viewModel.updateController(mockController)
@@ -263,10 +274,11 @@
     @Ignore
     fun updateSeekNotAvailable() {
         // GIVEN that seek is not included in actions
-        val state = PlaybackState.Builder().run {
-            setActions(PlaybackState.ACTION_PLAY)
-            build()
-        }
+        val state =
+            PlaybackState.Builder().run {
+                setActions(PlaybackState.ACTION_PLAY)
+                build()
+            }
         whenever(mockController.getPlaybackState()).thenReturn(state)
         // WHEN the controller is updated
         viewModel.updateController(mockController)
@@ -318,9 +330,7 @@
     @Ignore
     fun onSeekProgressWithSeekStarting() {
         val pos = 42L
-        with(viewModel) {
-            onSeekProgress(pos)
-        }
+        with(viewModel) { onSeekProgress(pos) }
         fakeExecutor.runAllReady()
         // THEN then elapsed time should not be updated
         assertThat(viewModel.progress.value!!.elapsedTime).isNull()
@@ -329,11 +339,12 @@
     @Test
     fun seekStarted_listenerNotified() {
         var isScrubbing: Boolean? = null
-        val listener = object : SeekBarViewModel.ScrubbingChangeListener {
-            override fun onScrubbingChanged(scrubbing: Boolean) {
-                isScrubbing = scrubbing
+        val listener =
+            object : SeekBarViewModel.ScrubbingChangeListener {
+                override fun onScrubbingChanged(scrubbing: Boolean) {
+                    isScrubbing = scrubbing
+                }
             }
-        }
         viewModel.setScrubbingChangeListener(listener)
 
         viewModel.onSeekStarting()
@@ -345,11 +356,12 @@
     @Test
     fun seekEnded_listenerNotified() {
         var isScrubbing: Boolean? = null
-        val listener = object : SeekBarViewModel.ScrubbingChangeListener {
-            override fun onScrubbingChanged(scrubbing: Boolean) {
-                isScrubbing = scrubbing
+        val listener =
+            object : SeekBarViewModel.ScrubbingChangeListener {
+                override fun onScrubbingChanged(scrubbing: Boolean) {
+                    isScrubbing = scrubbing
+                }
             }
-        }
         viewModel.setScrubbingChangeListener(listener)
 
         // Start seeking
@@ -385,9 +397,7 @@
         val bar = SeekBar(context)
 
         // WHEN we get an onProgressChanged event without an onStartTrackingTouch event
-        with(viewModel.seekBarListener) {
-            onProgressChanged(bar, pos, true)
-        }
+        with(viewModel.seekBarListener) { onProgressChanged(bar, pos, true) }
         fakeExecutor.runAllReady()
 
         // THEN we immediately update the transport
@@ -412,9 +422,7 @@
         viewModel.updateController(mockController)
         // WHEN user starts dragging the seek bar
         val pos = 42
-        val bar = SeekBar(context).apply {
-            progress = pos
-        }
+        val bar = SeekBar(context).apply { progress = pos }
         viewModel.seekBarListener.onStartTrackingTouch(bar)
         fakeExecutor.runAllReady()
         // THEN transport controls should be used
@@ -427,9 +435,7 @@
         viewModel.updateController(mockController)
         // WHEN user ends drag
         val pos = 42
-        val bar = SeekBar(context).apply {
-            progress = pos
-        }
+        val bar = SeekBar(context).apply { progress = pos }
         viewModel.seekBarListener.onStopTrackingTouch(bar)
         fakeExecutor.runAllReady()
         // THEN transport controls should be used
@@ -443,9 +449,7 @@
         // WHEN user starts dragging the seek bar
         val pos = 42
         val progPos = 84
-        val bar = SeekBar(context).apply {
-            progress = pos
-        }
+        val bar = SeekBar(context).apply { progress = pos }
         with(viewModel.seekBarListener) {
             onStartTrackingTouch(bar)
             onProgressChanged(bar, progPos, true)
@@ -478,10 +482,11 @@
     @Test
     fun queuePollTaskWhenPlaying() {
         // GIVEN that the track is playing
-        val state = PlaybackState.Builder().run {
-            setState(PlaybackState.STATE_PLAYING, 100L, 1f)
-            build()
-        }
+        val state =
+            PlaybackState.Builder().run {
+                setState(PlaybackState.STATE_PLAYING, 100L, 1f)
+                build()
+            }
         whenever(mockController.getPlaybackState()).thenReturn(state)
         // WHEN the controller is updated
         viewModel.updateController(mockController)
@@ -492,10 +497,11 @@
     @Test
     fun noQueuePollTaskWhenStopped() {
         // GIVEN that the playback state is stopped
-        val state = PlaybackState.Builder().run {
-            setState(PlaybackState.STATE_STOPPED, 200L, 1f)
-            build()
-        }
+        val state =
+            PlaybackState.Builder().run {
+                setState(PlaybackState.STATE_STOPPED, 200L, 1f)
+                build()
+            }
         whenever(mockController.getPlaybackState()).thenReturn(state)
         // WHEN updated
         viewModel.updateController(mockController)
@@ -512,10 +518,11 @@
             runAllReady()
         }
         // AND the playback state is playing
-        val state = PlaybackState.Builder().run {
-            setState(PlaybackState.STATE_PLAYING, 200L, 1f)
-            build()
-        }
+        val state =
+            PlaybackState.Builder().run {
+                setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+                build()
+            }
         whenever(mockController.getPlaybackState()).thenReturn(state)
         // WHEN updated
         viewModel.updateController(mockController)
@@ -532,10 +539,11 @@
             runAllReady()
         }
         // AND the playback state is playing
-        val state = PlaybackState.Builder().run {
-            setState(PlaybackState.STATE_PLAYING, 200L, 1f)
-            build()
-        }
+        val state =
+            PlaybackState.Builder().run {
+                setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+                build()
+            }
         whenever(mockController.getPlaybackState()).thenReturn(state)
         // WHEN updated
         viewModel.updateController(mockController)
@@ -546,10 +554,11 @@
     @Test
     fun pollTaskQueuesAnotherPollTaskWhenPlaying() {
         // GIVEN that the track is playing
-        val state = PlaybackState.Builder().run {
-            setState(PlaybackState.STATE_PLAYING, 100L, 1f)
-            build()
-        }
+        val state =
+            PlaybackState.Builder().run {
+                setState(PlaybackState.STATE_PLAYING, 100L, 1f)
+                build()
+            }
         whenever(mockController.getPlaybackState()).thenReturn(state)
         viewModel.updateController(mockController)
         // WHEN the next task runs
@@ -566,10 +575,11 @@
         // GIVEN listening
         viewModel.listening = true
         // AND the playback state is playing
-        val state = PlaybackState.Builder().run {
-            setState(PlaybackState.STATE_PLAYING, 200L, 1f)
-            build()
-        }
+        val state =
+            PlaybackState.Builder().run {
+                setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+                build()
+            }
         whenever(mockController.getPlaybackState()).thenReturn(state)
         viewModel.updateController(mockController)
         with(fakeExecutor) {
@@ -592,10 +602,11 @@
         // GIVEN listening
         viewModel.listening = true
         // AND the playback state is playing
-        val state = PlaybackState.Builder().run {
-            setState(PlaybackState.STATE_PLAYING, 200L, 1f)
-            build()
-        }
+        val state =
+            PlaybackState.Builder().run {
+                setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+                build()
+            }
         whenever(mockController.getPlaybackState()).thenReturn(state)
         viewModel.updateController(mockController)
         with(fakeExecutor) {
@@ -621,10 +632,11 @@
         // GIVEN listening
         viewModel.listening = true
         // AND the playback state is playing
-        val state = PlaybackState.Builder().run {
-            setState(PlaybackState.STATE_PLAYING, 200L, 1f)
-            build()
-        }
+        val state =
+            PlaybackState.Builder().run {
+                setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+                build()
+            }
         whenever(mockController.getPlaybackState()).thenReturn(state)
         viewModel.updateController(mockController)
         with(fakeExecutor) {
@@ -654,10 +666,11 @@
             runAllReady()
         }
         // AND the playback state is playing
-        val state = PlaybackState.Builder().run {
-            setState(PlaybackState.STATE_STOPPED, 200L, 1f)
-            build()
-        }
+        val state =
+            PlaybackState.Builder().run {
+                setState(PlaybackState.STATE_STOPPED, 200L, 1f)
+                build()
+            }
         whenever(mockController.getPlaybackState()).thenReturn(state)
         viewModel.updateController(mockController)
         // WHEN start listening
@@ -673,10 +686,11 @@
         verify(mockController).registerCallback(captor.capture())
         val callback = captor.value
         // WHEN the callback receives an new state
-        val state = PlaybackState.Builder().run {
-            setState(PlaybackState.STATE_PLAYING, 100L, 1f)
-            build()
-        }
+        val state =
+            PlaybackState.Builder().run {
+                setState(PlaybackState.STATE_PLAYING, 100L, 1f)
+                build()
+            }
         callback.onPlaybackStateChanged(state)
         with(fakeExecutor) {
             advanceClockToNext()
@@ -690,16 +704,18 @@
     @Ignore
     fun clearSeekBar() {
         // GIVEN that the duration is contained within the metadata
-        val metadata = MediaMetadata.Builder().run {
-            putLong(MediaMetadata.METADATA_KEY_DURATION, 12000L)
-            build()
-        }
+        val metadata =
+            MediaMetadata.Builder().run {
+                putLong(MediaMetadata.METADATA_KEY_DURATION, 12000L)
+                build()
+            }
         whenever(mockController.getMetadata()).thenReturn(metadata)
         // AND a valid playback state (ie. media session is not destroyed)
-        val state = PlaybackState.Builder().run {
-            setState(PlaybackState.STATE_PLAYING, 200L, 1f)
-            build()
-        }
+        val state =
+            PlaybackState.Builder().run {
+                setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+                build()
+            }
         whenever(mockController.getPlaybackState()).thenReturn(state)
         // AND the controller has been updated
         viewModel.updateController(mockController)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SmartspaceMediaDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt
similarity index 62%
rename from packages/SystemUI/tests/src/com/android/systemui/media/SmartspaceMediaDataTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt
index b5078bc..1d6e980 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SmartspaceMediaDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt
@@ -1,4 +1,20 @@
-package com.android.systemui.media
+/*
+ * 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.media.controls.models.recommendation
 
 import android.app.smartspace.SmartspaceAction
 import android.graphics.drawable.Icon
@@ -36,11 +52,11 @@
 
     @Test
     fun isValid_tooFewRecs_returnsFalse() {
-        val data = DEFAULT_DATA.copy(
-            recommendations = listOf(
-                SmartspaceAction.Builder("id", "title").setIcon(icon).build()
+        val data =
+            DEFAULT_DATA.copy(
+                recommendations =
+                    listOf(SmartspaceAction.Builder("id", "title").setIcon(icon).build())
             )
-        )
 
         assertThat(data.isValid()).isFalse()
     }
@@ -50,14 +66,10 @@
         val recommendations = mutableListOf<SmartspaceAction>()
         // Add one fewer recommendation w/ icon than the number required
         for (i in 1 until NUM_REQUIRED_RECOMMENDATIONS) {
-            recommendations.add(
-                SmartspaceAction.Builder("id", "title").setIcon(icon).build()
-            )
+            recommendations.add(SmartspaceAction.Builder("id", "title").setIcon(icon).build())
         }
         for (i in 1 until 3) {
-            recommendations.add(
-                SmartspaceAction.Builder("id", "title").setIcon(null).build()
-            )
+            recommendations.add(SmartspaceAction.Builder("id", "title").setIcon(null).build())
         }
 
         val data = DEFAULT_DATA.copy(recommendations = recommendations)
@@ -70,9 +82,7 @@
         val recommendations = mutableListOf<SmartspaceAction>()
         // Add the number of required recommendations
         for (i in 0 until NUM_REQUIRED_RECOMMENDATIONS) {
-            recommendations.add(
-                SmartspaceAction.Builder("id", "title").setIcon(icon).build()
-            )
+            recommendations.add(SmartspaceAction.Builder("id", "title").setIcon(icon).build())
         }
 
         val data = DEFAULT_DATA.copy(recommendations = recommendations)
@@ -85,9 +95,7 @@
         val recommendations = mutableListOf<SmartspaceAction>()
         // Add more than enough recommendations
         for (i in 0 until NUM_REQUIRED_RECOMMENDATIONS + 3) {
-            recommendations.add(
-                SmartspaceAction.Builder("id", "title").setIcon(icon).build()
-            )
+            recommendations.add(SmartspaceAction.Builder("id", "title").setIcon(icon).build())
         }
 
         val data = DEFAULT_DATA.copy(recommendations = recommendations)
@@ -96,13 +104,14 @@
     }
 }
 
-private val DEFAULT_DATA = SmartspaceMediaData(
-    targetId = "INVALID",
-    isActive = false,
-    packageName = "INVALID",
-    cardAction = null,
-    recommendations = emptyList(),
-    dismissIntent = null,
-    headphoneConnectionTimeMillis = 0,
-    instanceId = InstanceId.fakeInstanceId(-1)
-)
+private val DEFAULT_DATA =
+    SmartspaceMediaData(
+        targetId = "INVALID",
+        isActive = false,
+        packageName = "INVALID",
+        cardAction = null,
+        recommendations = emptyList(),
+        dismissIntent = null,
+        headphoneConnectionTimeMillis = 0,
+        instanceId = InstanceId.fakeInstanceId(-1)
+    )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
index 04b93d7..4d2d0f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media;
+package com.android.systemui.media.controls.pipeline;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -26,7 +26,6 @@
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 
-import android.graphics.Color;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -34,6 +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 org.junit.Before;
 import org.junit.Rule;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
similarity index 82%
rename from packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
index 6468fe1..575b1c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.pipeline
 
 import android.app.smartspace.SmartspaceAction
 import android.testing.AndroidTestingRunner
@@ -24,6 +24,11 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 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.SmartspaceMediaData
+import com.android.systemui.media.controls.ui.MediaPlayerData
+import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -58,24 +63,15 @@
 @TestableLooper.RunWithLooper
 class MediaDataFilterTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var listener: MediaDataManager.Listener
-    @Mock
-    private lateinit var broadcastDispatcher: BroadcastDispatcher
-    @Mock
-    private lateinit var broadcastSender: BroadcastSender
-    @Mock
-    private lateinit var mediaDataManager: MediaDataManager
-    @Mock
-    private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
-    @Mock
-    private lateinit var executor: Executor
-    @Mock
-    private lateinit var smartspaceData: SmartspaceMediaData
-    @Mock
-    private lateinit var smartspaceMediaRecommendationItem: SmartspaceAction
-    @Mock
-    private lateinit var logger: MediaUiEventLogger
+    @Mock private lateinit var listener: MediaDataManager.Listener
+    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock private lateinit var broadcastSender: BroadcastSender
+    @Mock private lateinit var mediaDataManager: MediaDataManager
+    @Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
+    @Mock private lateinit var executor: Executor
+    @Mock private lateinit var smartspaceData: SmartspaceMediaData
+    @Mock private lateinit var smartspaceMediaRecommendationItem: SmartspaceAction
+    @Mock private lateinit var logger: MediaUiEventLogger
 
     private lateinit var mediaDataFilter: MediaDataFilter
     private lateinit var dataMain: MediaData
@@ -86,14 +82,16 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
         MediaPlayerData.clear()
-        mediaDataFilter = MediaDataFilter(
-            context,
-            broadcastDispatcher,
-            broadcastSender,
-            lockscreenUserManager,
-            executor,
-            clock,
-            logger)
+        mediaDataFilter =
+            MediaDataFilter(
+                context,
+                broadcastDispatcher,
+                broadcastSender,
+                lockscreenUserManager,
+                executor,
+                clock,
+                logger
+            )
         mediaDataFilter.mediaDataManager = mediaDataManager
         mediaDataFilter.addListener(listener)
 
@@ -101,11 +99,13 @@
         setUser(USER_MAIN)
 
         // Set up test media data
-        dataMain = MediaTestUtils.emptyMediaData.copy(
+        dataMain =
+            MediaTestUtils.emptyMediaData.copy(
                 userId = USER_MAIN,
                 packageName = PACKAGE,
                 instanceId = INSTANCE_ID,
-                appUid = APP_UID)
+                appUid = APP_UID
+            )
         dataGuest = dataMain.copy(userId = USER_GUEST)
 
         `when`(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY)
@@ -113,8 +113,8 @@
         `when`(smartspaceData.isValid()).thenReturn(true)
         `when`(smartspaceData.packageName).thenReturn(SMARTSPACE_PACKAGE)
         `when`(smartspaceData.recommendations).thenReturn(listOf(smartspaceMediaRecommendationItem))
-        `when`(smartspaceData.headphoneConnectionTimeMillis).thenReturn(
-                clock.currentTimeMillis() - 100)
+        `when`(smartspaceData.headphoneConnectionTimeMillis)
+            .thenReturn(clock.currentTimeMillis() - 100)
         `when`(smartspaceData.instanceId).thenReturn(SMARTSPACE_INSTANCE_ID)
     }
 
@@ -130,8 +130,8 @@
         mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
 
         // THEN we should tell the listener
-        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true),
-                eq(0), eq(false))
+        verify(listener)
+            .onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true), eq(0), eq(false))
     }
 
     @Test
@@ -140,8 +140,8 @@
         mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
 
         // THEN we should NOT tell the listener
-        verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean(),
-                anyInt(), anyBoolean())
+        verify(listener, never())
+            .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
     }
 
     @Test
@@ -187,12 +187,12 @@
         setUser(USER_GUEST)
 
         // THEN we should add back the guest user media
-        verify(listener).onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest), eq(true),
-                eq(0), eq(false))
+        verify(listener)
+            .onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest), eq(true), eq(0), eq(false))
 
         // but not the main user's
-        verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), eq(dataMain), anyBoolean(),
-                anyInt(), anyBoolean())
+        verify(listener, never())
+            .onMediaDataLoaded(eq(KEY), any(), eq(dataMain), anyBoolean(), anyInt(), anyBoolean())
     }
 
     @Test
@@ -340,7 +340,7 @@
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
         verify(listener)
-                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
+            .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
         assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
         assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
         verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
@@ -353,8 +353,8 @@
 
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
-        verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean(),
-                anyInt(), anyBoolean())
+        verify(listener, never())
+            .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
         verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
         assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
         assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
@@ -370,7 +370,7 @@
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
         verify(listener)
-                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
+            .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
         assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
         assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
         verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
@@ -400,15 +400,15 @@
         // WHEN we have media that was recently played, but not currently active
         val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
         mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
-        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true),
-                eq(0), eq(false))
+        verify(listener)
+            .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
 
         // AND we get a smartspace signal
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
         // THEN we should tell listeners to treat the media as not active instead
-        verify(listener, never()).onMediaDataLoaded(eq(KEY), eq(KEY), any(), anyBoolean(),
-                anyInt(), anyBoolean())
+        verify(listener, never())
+            .onMediaDataLoaded(eq(KEY), eq(KEY), any(), anyBoolean(), anyInt(), anyBoolean())
         verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
         assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
         assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
@@ -423,16 +423,23 @@
         // WHEN we have media that was recently played, but not currently active
         val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
         mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
-        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true),
-                eq(0), eq(false))
+        verify(listener)
+            .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
 
         // AND we get a smartspace signal
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
         // THEN we should tell listeners to treat the media as active instead
         val dataCurrentAndActive = dataCurrent.copy(active = true)
-        verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true),
-                eq(100), eq(true))
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(KEY),
+                eq(KEY),
+                eq(dataCurrentAndActive),
+                eq(true),
+                eq(100),
+                eq(true)
+            )
         assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
         // Smartspace update shouldn't be propagated for the empty rec list.
         verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
@@ -445,20 +452,27 @@
         // WHEN we have media that was recently played, but not currently active
         val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
         mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
-        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true),
-                eq(0), eq(false))
+        verify(listener)
+            .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
 
         // AND we get a smartspace signal
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
         // THEN we should tell listeners to treat the media as active instead
         val dataCurrentAndActive = dataCurrent.copy(active = true)
-        verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true),
-                eq(100), eq(true))
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(KEY),
+                eq(KEY),
+                eq(dataCurrentAndActive),
+                eq(true),
+                eq(100),
+                eq(true)
+            )
         assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
         // Smartspace update should also be propagated but not prioritized.
         verify(listener)
-                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
+            .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
         verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
         verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID))
     }
@@ -477,14 +491,21 @@
     fun testOnSmartspaceMediaDataRemoved_usedMediaAndSmartspace_clearsBoth() {
         val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
         mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
-        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true),
-                eq(0), eq(false))
+        verify(listener)
+            .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
 
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
         val dataCurrentAndActive = dataCurrent.copy(active = true)
-        verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true),
-                eq(100), eq(true))
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(KEY),
+                eq(KEY),
+                eq(dataCurrentAndActive),
+                eq(true),
+                eq(100),
+                eq(true)
+            )
 
         mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
similarity index 60%
rename from packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index f9c7d2d..11eb26b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -1,4 +1,20 @@
-package com.android.systemui.media
+/*
+ * 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.media.controls.pipeline
 
 import android.app.Notification
 import android.app.Notification.MediaStyle
@@ -26,6 +42,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.SmartspaceMediaData
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider
+import com.android.systemui.media.controls.resume.MediaResumeListener
+import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaFlags
+import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.SbnBuilder
 import com.android.systemui.tuner.TunerService
@@ -111,58 +134,68 @@
 
     private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
 
-    private val originalSmartspaceSetting = Settings.Secure.getInt(context.contentResolver,
-            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 1)
+    private val originalSmartspaceSetting =
+        Settings.Secure.getInt(
+            context.contentResolver,
+            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
+            1
+        )
 
     @Before
     fun setup() {
         foregroundExecutor = FakeExecutor(clock)
         backgroundExecutor = FakeExecutor(clock)
         smartspaceMediaDataProvider = SmartspaceMediaDataProvider()
-        Settings.Secure.putInt(context.contentResolver,
-                Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 1)
-        mediaDataManager = MediaDataManager(
-            context = context,
-            backgroundExecutor = backgroundExecutor,
-            foregroundExecutor = foregroundExecutor,
-            mediaControllerFactory = mediaControllerFactory,
-            broadcastDispatcher = broadcastDispatcher,
-            dumpManager = dumpManager,
-            mediaTimeoutListener = mediaTimeoutListener,
-            mediaResumeListener = mediaResumeListener,
-            mediaSessionBasedFilter = mediaSessionBasedFilter,
-            mediaDeviceManager = mediaDeviceManager,
-            mediaDataCombineLatest = mediaDataCombineLatest,
-            mediaDataFilter = mediaDataFilter,
-            activityStarter = activityStarter,
-            smartspaceMediaDataProvider = smartspaceMediaDataProvider,
-            useMediaResumption = true,
-            useQsMediaPlayer = true,
-            systemClock = clock,
-            tunerService = tunerService,
-            mediaFlags = mediaFlags,
-            logger = logger
+        Settings.Secure.putInt(
+            context.contentResolver,
+            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
+            1
         )
-        verify(tunerService).addTunable(capture(tunableCaptor),
-                eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
+        mediaDataManager =
+            MediaDataManager(
+                context = context,
+                backgroundExecutor = backgroundExecutor,
+                foregroundExecutor = foregroundExecutor,
+                mediaControllerFactory = mediaControllerFactory,
+                broadcastDispatcher = broadcastDispatcher,
+                dumpManager = dumpManager,
+                mediaTimeoutListener = mediaTimeoutListener,
+                mediaResumeListener = mediaResumeListener,
+                mediaSessionBasedFilter = mediaSessionBasedFilter,
+                mediaDeviceManager = mediaDeviceManager,
+                mediaDataCombineLatest = mediaDataCombineLatest,
+                mediaDataFilter = mediaDataFilter,
+                activityStarter = activityStarter,
+                smartspaceMediaDataProvider = smartspaceMediaDataProvider,
+                useMediaResumption = true,
+                useQsMediaPlayer = true,
+                systemClock = clock,
+                tunerService = tunerService,
+                mediaFlags = mediaFlags,
+                logger = logger
+            )
+        verify(tunerService)
+            .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
         session = MediaSession(context, "MediaDataManagerTestSession")
-        mediaNotification = SbnBuilder().run {
-            setPkg(PACKAGE_NAME)
-            modifyNotification(context).also {
-                it.setSmallIcon(android.R.drawable.ic_media_pause)
-                it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+        mediaNotification =
+            SbnBuilder().run {
+                setPkg(PACKAGE_NAME)
+                modifyNotification(context).also {
+                    it.setSmallIcon(android.R.drawable.ic_media_pause)
+                    it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+                }
+                build()
             }
-            build()
-        }
-        metadataBuilder = MediaMetadata.Builder().apply {
-            putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
-            putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
-        }
+        metadataBuilder =
+            MediaMetadata.Builder().apply {
+                putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
+                putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
+            }
         whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
         whenever(controller.transportControls).thenReturn(transportControls)
         whenever(controller.playbackInfo).thenReturn(playbackInfo)
-        whenever(playbackInfo.playbackType).thenReturn(
-                MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL)
+        whenever(playbackInfo.playbackType)
+            .thenReturn(MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL)
 
         // This is an ugly hack for now. The mediaSessionBasedFilter is one of the internal
         // listeners in the internal processing pipeline. It receives events, but ince it is a
@@ -170,18 +203,18 @@
         // treat mediaSessionBasedFilter as a listener for testing.
         listener = mediaSessionBasedFilter
 
-        val recommendationExtras = Bundle().apply {
-            putString("package_name", PACKAGE_NAME)
-            putParcelable("dismiss_intent", DISMISS_INTENT)
-        }
+        val recommendationExtras =
+            Bundle().apply {
+                putString("package_name", PACKAGE_NAME)
+                putParcelable("dismiss_intent", DISMISS_INTENT)
+            }
         val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play)
         whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras)
         whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction)
         whenever(mediaRecommendationItem.extras).thenReturn(recommendationExtras)
         whenever(mediaRecommendationItem.icon).thenReturn(icon)
-        validRecommendationList = listOf(
-            mediaRecommendationItem, mediaRecommendationItem, mediaRecommendationItem
-        )
+        validRecommendationList =
+            listOf(mediaRecommendationItem, mediaRecommendationItem, mediaRecommendationItem)
         whenever(mediaSmartspaceTarget.smartspaceTargetId).thenReturn(KEY_MEDIA_SMARTSPACE)
         whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA)
         whenever(mediaSmartspaceTarget.iconGrid).thenReturn(validRecommendationList)
@@ -194,8 +227,11 @@
     fun tearDown() {
         session.release()
         mediaDataManager.destroy()
-        Settings.Secure.putInt(context.contentResolver,
-                Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, originalSmartspaceSetting)
+        Settings.Secure.putInt(
+            context.contentResolver,
+            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
+            originalSmartspaceSetting
+        )
     }
 
     @Test
@@ -212,21 +248,36 @@
     @Test
     fun testSetTimedOut_resume_dismissesMedia() {
         // WHEN resume controls are present, and time out
-        val desc = MediaDescription.Builder().run {
-            setTitle(SESSION_TITLE)
-            build()
-        }
-        mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
-                APP_NAME, pendingIntent, PACKAGE_NAME)
+        val desc =
+            MediaDescription.Builder().run {
+                setTitle(SESSION_TITLE)
+                build()
+            }
+        mediaDataManager.addResumptionControls(
+            USER_ID,
+            desc,
+            Runnable {},
+            session.sessionToken,
+            APP_NAME,
+            pendingIntent,
+            PACKAGE_NAME
+        )
 
         backgroundExecutor.runAllReady()
         foregroundExecutor.runAllReady()
-        verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor),
-            eq(true), eq(0), eq(false))
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(PACKAGE_NAME),
+                eq(null),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
 
         mediaDataManager.setTimedOut(PACKAGE_NAME, timedOut = true)
-        verify(logger).logMediaTimeout(anyInt(), eq(PACKAGE_NAME),
-            eq(mediaDataCaptor.value.instanceId))
+        verify(logger)
+            .logMediaTimeout(anyInt(), eq(PACKAGE_NAME), eq(mediaDataCaptor.value.instanceId))
 
         // THEN it is removed and listeners are informed
         foregroundExecutor.advanceClockToLast()
@@ -243,8 +294,13 @@
     @Test
     fun testOnMetaDataLoaded_callsListener() {
         addNotificationAndLoad()
-        verify(logger).logActiveMediaAdded(anyInt(), eq(PACKAGE_NAME),
-            eq(mediaDataCaptor.value.instanceId), eq(MediaData.PLAYBACK_LOCAL))
+        verify(logger)
+            .logActiveMediaAdded(
+                anyInt(),
+                eq(PACKAGE_NAME),
+                eq(mediaDataCaptor.value.instanceId),
+                eq(MediaData.PLAYBACK_LOCAL)
+            )
     }
 
     @Test
@@ -255,56 +311,85 @@
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-                eq(0), eq(false))
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(KEY),
+                eq(null),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
         assertThat(mediaDataCaptor.value!!.active).isTrue()
     }
 
     @Test
     fun testOnNotificationAdded_isRcn_markedRemote() {
-        val rcn = SbnBuilder().run {
-            setPkg(SYSTEM_PACKAGE_NAME)
-            modifyNotification(context).also {
-                it.setSmallIcon(android.R.drawable.ic_media_pause)
-                it.setStyle(MediaStyle().apply {
-                    setMediaSession(session.sessionToken)
-                    setRemotePlaybackInfo("Remote device", 0, null)
-                })
+        val rcn =
+            SbnBuilder().run {
+                setPkg(SYSTEM_PACKAGE_NAME)
+                modifyNotification(context).also {
+                    it.setSmallIcon(android.R.drawable.ic_media_pause)
+                    it.setStyle(
+                        MediaStyle().apply {
+                            setMediaSession(session.sessionToken)
+                            setRemotePlaybackInfo("Remote device", 0, null)
+                        }
+                    )
+                }
+                build()
             }
-            build()
-        }
 
         mediaDataManager.onNotificationAdded(KEY, rcn)
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-                eq(0), eq(false))
-        assertThat(mediaDataCaptor.value!!.playbackLocation).isEqualTo(
-                MediaData.PLAYBACK_CAST_REMOTE)
-        verify(logger).logActiveMediaAdded(anyInt(), eq(SYSTEM_PACKAGE_NAME),
-            eq(mediaDataCaptor.value.instanceId), eq(MediaData.PLAYBACK_CAST_REMOTE))
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(KEY),
+                eq(null),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
+        assertThat(mediaDataCaptor.value!!.playbackLocation)
+            .isEqualTo(MediaData.PLAYBACK_CAST_REMOTE)
+        verify(logger)
+            .logActiveMediaAdded(
+                anyInt(),
+                eq(SYSTEM_PACKAGE_NAME),
+                eq(mediaDataCaptor.value.instanceId),
+                eq(MediaData.PLAYBACK_CAST_REMOTE)
+            )
     }
 
     @Test
     fun testOnNotificationAdded_hasSubstituteName_isUsed() {
         val subName = "Substitute Name"
-        val notif = SbnBuilder().run {
-            modifyNotification(context).also {
-                it.extras = Bundle().apply {
-                    putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, subName)
+        val notif =
+            SbnBuilder().run {
+                modifyNotification(context).also {
+                    it.extras =
+                        Bundle().apply {
+                            putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, subName)
+                        }
+                    it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
                 }
-                it.setStyle(MediaStyle().apply {
-                    setMediaSession(session.sessionToken)
-                })
+                build()
             }
-            build()
-        }
 
         mediaDataManager.onNotificationAdded(KEY, notif)
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-            eq(0), eq(false))
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(KEY),
+                eq(null),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
 
         assertThat(mediaDataCaptor.value!!.app).isEqualTo(subName)
     }
@@ -314,17 +399,18 @@
         val bundle = Bundle()
         // wrong data type
         bundle.putParcelable(Notification.EXTRA_MEDIA_SESSION, Bundle())
-        val rcn = SbnBuilder().run {
-            setPkg(SYSTEM_PACKAGE_NAME)
-            modifyNotification(context).also {
-                it.setSmallIcon(android.R.drawable.ic_media_pause)
-                it.addExtras(bundle)
-                it.setStyle(MediaStyle().apply {
-                    setRemotePlaybackInfo("Remote device", 0, null)
-                })
+        val rcn =
+            SbnBuilder().run {
+                setPkg(SYSTEM_PACKAGE_NAME)
+                modifyNotification(context).also {
+                    it.setSmallIcon(android.R.drawable.ic_media_pause)
+                    it.addExtras(bundle)
+                    it.setStyle(
+                        MediaStyle().apply { setRemotePlaybackInfo("Remote device", 0, null) }
+                    )
+                }
+                build()
             }
-            build()
-        }
 
         mediaDataManager.loadMediaDataInBg(KEY, rcn, null)
         // no crash even though the data structure is incorrect
@@ -335,18 +421,21 @@
         val bundle = Bundle()
         // wrong data type
         bundle.putParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT, Bundle())
-        val rcn = SbnBuilder().run {
-            setPkg(SYSTEM_PACKAGE_NAME)
-            modifyNotification(context).also {
-                it.setSmallIcon(android.R.drawable.ic_media_pause)
-                it.addExtras(bundle)
-                it.setStyle(MediaStyle().apply {
-                    setMediaSession(session.sessionToken)
-                    setRemotePlaybackInfo("Remote device", 0, null)
-                })
+        val rcn =
+            SbnBuilder().run {
+                setPkg(SYSTEM_PACKAGE_NAME)
+                modifyNotification(context).also {
+                    it.setSmallIcon(android.R.drawable.ic_media_pause)
+                    it.addExtras(bundle)
+                    it.setStyle(
+                        MediaStyle().apply {
+                            setMediaSession(session.sessionToken)
+                            setRemotePlaybackInfo("Remote device", 0, null)
+                        }
+                    )
+                }
+                build()
             }
-            build()
-        }
 
         mediaDataManager.loadMediaDataInBg(KEY, rcn, null)
         // no crash even though the data structure is incorrect
@@ -373,8 +462,14 @@
         mediaDataManager.onNotificationRemoved(KEY)
         // THEN the media data indicates that it is for resumption
         verify(listener)
-            .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
-                    eq(0), eq(false))
+            .onMediaDataLoaded(
+                eq(PACKAGE_NAME),
+                eq(KEY),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
         assertThat(mediaDataCaptor.value.resumption).isTrue()
         assertThat(mediaDataCaptor.value.isPlaying).isFalse()
         verify(logger).logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
@@ -389,8 +484,14 @@
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(2)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(2)
         verify(listener)
-            .onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-                    eq(0), eq(false))
+            .onMediaDataLoaded(
+                eq(KEY),
+                eq(null),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
         val data = mediaDataCaptor.value
         assertThat(data.resumption).isFalse()
         val resumableData = data.copy(resumeAction = Runnable {})
@@ -401,8 +502,14 @@
         mediaDataManager.onNotificationRemoved(KEY)
         // THEN the data is for resumption and the key is migrated to the package name
         verify(listener)
-            .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
-                    eq(0), eq(false))
+            .onMediaDataLoaded(
+                eq(PACKAGE_NAME),
+                eq(KEY),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
         assertThat(mediaDataCaptor.value.resumption).isTrue()
         verify(listener, never()).onMediaDataRemoved(eq(KEY))
         // WHEN the second is removed
@@ -410,8 +517,13 @@
         // THEN the data is for resumption and the second key is removed
         verify(listener)
             .onMediaDataLoaded(
-                eq(PACKAGE_NAME), eq(PACKAGE_NAME), capture(mediaDataCaptor), eq(true),
-                    eq(0), eq(false))
+                eq(PACKAGE_NAME),
+                eq(PACKAGE_NAME),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
         assertThat(mediaDataCaptor.value.resumption).isTrue()
         verify(listener).onMediaDataRemoved(eq(KEY_2))
     }
@@ -420,15 +532,20 @@
     fun testOnNotificationRemoved_withResumption_butNotLocal() {
         // GIVEN that the manager has a notification with a resume action, but is not local
         whenever(controller.metadata).thenReturn(metadataBuilder.build())
-        whenever(playbackInfo.playbackType).thenReturn(
-                MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+        whenever(playbackInfo.playbackType)
+            .thenReturn(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE)
         addNotificationAndLoad()
         val data = mediaDataCaptor.value
-        val dataRemoteWithResume = data.copy(resumeAction = Runnable {},
-                playbackLocation = MediaData.PLAYBACK_CAST_LOCAL)
+        val dataRemoteWithResume =
+            data.copy(resumeAction = Runnable {}, playbackLocation = MediaData.PLAYBACK_CAST_LOCAL)
         mediaDataManager.onMediaDataLoaded(KEY, null, dataRemoteWithResume)
-        verify(logger).logActiveMediaAdded(anyInt(), eq(PACKAGE_NAME),
-            eq(mediaDataCaptor.value.instanceId), eq(MediaData.PLAYBACK_CAST_LOCAL))
+        verify(logger)
+            .logActiveMediaAdded(
+                anyInt(),
+                eq(PACKAGE_NAME),
+                eq(mediaDataCaptor.value.instanceId),
+                eq(MediaData.PLAYBACK_CAST_LOCAL)
+            )
 
         // WHEN the notification is removed
         mediaDataManager.onNotificationRemoved(KEY)
@@ -440,19 +557,33 @@
     @Test
     fun testAddResumptionControls() {
         // WHEN resumption controls are added
-        val desc = MediaDescription.Builder().run {
-            setTitle(SESSION_TITLE)
-            build()
-        }
+        val desc =
+            MediaDescription.Builder().run {
+                setTitle(SESSION_TITLE)
+                build()
+            }
         val currentTime = clock.elapsedRealtime()
-        mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
-                APP_NAME, pendingIntent, PACKAGE_NAME)
+        mediaDataManager.addResumptionControls(
+            USER_ID,
+            desc,
+            Runnable {},
+            session.sessionToken,
+            APP_NAME,
+            pendingIntent,
+            PACKAGE_NAME
+        )
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         // THEN the media data indicates that it is for resumption
         verify(listener)
-            .onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor), eq(true),
-                    eq(0), eq(false))
+            .onMediaDataLoaded(
+                eq(PACKAGE_NAME),
+                eq(null),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
         val data = mediaDataCaptor.value
         assertThat(data.resumption).isTrue()
         assertThat(data.song).isEqualTo(SESSION_TITLE)
@@ -466,16 +597,31 @@
     @Test
     fun testResumptionDisabled_dismissesResumeControls() {
         // WHEN there are resume controls and resumption is switched off
-        val desc = MediaDescription.Builder().run {
-            setTitle(SESSION_TITLE)
-            build()
-        }
-        mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
-            APP_NAME, pendingIntent, PACKAGE_NAME)
+        val desc =
+            MediaDescription.Builder().run {
+                setTitle(SESSION_TITLE)
+                build()
+            }
+        mediaDataManager.addResumptionControls(
+            USER_ID,
+            desc,
+            Runnable {},
+            session.sessionToken,
+            APP_NAME,
+            pendingIntent,
+            PACKAGE_NAME
+        )
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor),
-            eq(true), eq(0), eq(false))
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(PACKAGE_NAME),
+                eq(null),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
         val data = mediaDataCaptor.value
         mediaDataManager.setMediaResumptionEnabled(false)
 
@@ -508,23 +654,30 @@
     fun testBadArtwork_doesNotUse() {
         // WHEN notification has a too-small artwork
         val artwork = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
-        val notif = SbnBuilder().run {
-            setPkg(PACKAGE_NAME)
-            modifyNotification(context).also {
-                it.setSmallIcon(android.R.drawable.ic_media_pause)
-                it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
-                it.setLargeIcon(artwork)
+        val notif =
+            SbnBuilder().run {
+                setPkg(PACKAGE_NAME)
+                modifyNotification(context).also {
+                    it.setSmallIcon(android.R.drawable.ic_media_pause)
+                    it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+                    it.setLargeIcon(artwork)
+                }
+                build()
             }
-            build()
-        }
         mediaDataManager.onNotificationAdded(KEY, notif)
 
         // THEN it still loads
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         verify(listener)
-            .onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-                    eq(0), eq(false))
+            .onMediaDataLoaded(
+                eq(KEY),
+                eq(null),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
     }
 
     @Test
@@ -533,18 +686,23 @@
         verify(logger).getNewInstanceId()
         val instanceId = instanceIdSequence.lastInstanceId
 
-        verify(listener).onSmartspaceMediaDataLoaded(
-            eq(KEY_MEDIA_SMARTSPACE),
-            eq(SmartspaceMediaData(
-                targetId = KEY_MEDIA_SMARTSPACE,
-                isActive = true,
-                packageName = PACKAGE_NAME,
-                cardAction = mediaSmartspaceBaseAction,
-                recommendations = validRecommendationList,
-                dismissIntent = DISMISS_INTENT,
-                headphoneConnectionTimeMillis = 1234L,
-                instanceId = InstanceId.fakeInstanceId(instanceId))),
-            eq(false))
+        verify(listener)
+            .onSmartspaceMediaDataLoaded(
+                eq(KEY_MEDIA_SMARTSPACE),
+                eq(
+                    SmartspaceMediaData(
+                        targetId = KEY_MEDIA_SMARTSPACE,
+                        isActive = true,
+                        packageName = PACKAGE_NAME,
+                        cardAction = mediaSmartspaceBaseAction,
+                        recommendations = validRecommendationList,
+                        dismissIntent = DISMISS_INTENT,
+                        headphoneConnectionTimeMillis = 1234L,
+                        instanceId = InstanceId.fakeInstanceId(instanceId)
+                    )
+                ),
+                eq(false)
+            )
     }
 
     @Test
@@ -554,23 +712,29 @@
         verify(logger).getNewInstanceId()
         val instanceId = instanceIdSequence.lastInstanceId
 
-        verify(listener).onSmartspaceMediaDataLoaded(
-            eq(KEY_MEDIA_SMARTSPACE),
-            eq(EMPTY_SMARTSPACE_MEDIA_DATA.copy(
-                targetId = KEY_MEDIA_SMARTSPACE,
-                isActive = true,
-                dismissIntent = DISMISS_INTENT,
-                headphoneConnectionTimeMillis = 1234L,
-                instanceId = InstanceId.fakeInstanceId(instanceId))),
-            eq(false))
+        verify(listener)
+            .onSmartspaceMediaDataLoaded(
+                eq(KEY_MEDIA_SMARTSPACE),
+                eq(
+                    EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+                        targetId = KEY_MEDIA_SMARTSPACE,
+                        isActive = true,
+                        dismissIntent = DISMISS_INTENT,
+                        headphoneConnectionTimeMillis = 1234L,
+                        instanceId = InstanceId.fakeInstanceId(instanceId)
+                    )
+                ),
+                eq(false)
+            )
     }
 
     @Test
     fun testOnSmartspaceMediaDataLoaded_hasNullIntent_callsListener() {
-        val recommendationExtras = Bundle().apply {
-            putString("package_name", PACKAGE_NAME)
-            putParcelable("dismiss_intent", null)
-        }
+        val recommendationExtras =
+            Bundle().apply {
+                putString("package_name", PACKAGE_NAME)
+                putParcelable("dismiss_intent", null)
+            }
         whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras)
         whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction)
         whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
@@ -579,15 +743,20 @@
         verify(logger).getNewInstanceId()
         val instanceId = instanceIdSequence.lastInstanceId
 
-        verify(listener).onSmartspaceMediaDataLoaded(
-            eq(KEY_MEDIA_SMARTSPACE),
-            eq(EMPTY_SMARTSPACE_MEDIA_DATA.copy(
-                targetId = KEY_MEDIA_SMARTSPACE,
-                isActive = true,
-                dismissIntent = null,
-                headphoneConnectionTimeMillis = 1234L,
-                instanceId = InstanceId.fakeInstanceId(instanceId))),
-            eq(false))
+        verify(listener)
+            .onSmartspaceMediaDataLoaded(
+                eq(KEY_MEDIA_SMARTSPACE),
+                eq(
+                    EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+                        targetId = KEY_MEDIA_SMARTSPACE,
+                        isActive = true,
+                        dismissIntent = null,
+                        headphoneConnectionTimeMillis = 1234L,
+                        instanceId = InstanceId.fakeInstanceId(instanceId)
+                    )
+                ),
+                eq(false)
+            )
     }
 
     @Test
@@ -595,7 +764,7 @@
         smartspaceMediaDataProvider.onTargetsAvailable(listOf())
         verify(logger, never()).getNewInstanceId()
         verify(listener, never())
-                .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
+            .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
     }
 
     @Ignore("b/233283726")
@@ -615,15 +784,18 @@
     @Test
     fun testOnSmartspaceMediaDataLoaded_settingDisabled_doesNothing() {
         // WHEN media recommendation setting is off
-        Settings.Secure.putInt(context.contentResolver,
-                Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0)
+        Settings.Secure.putInt(
+            context.contentResolver,
+            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
+            0
+        )
         tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
 
         smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
 
         // THEN smartspace signal is ignored
         verify(listener, never())
-                .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
+            .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
     }
 
     @Ignore("b/229838140")
@@ -631,12 +803,15 @@
     fun testMediaRecommendationDisabled_removesSmartspaceData() {
         // GIVEN a media recommendation card is present
         smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-        verify(listener).onSmartspaceMediaDataLoaded(eq(KEY_MEDIA_SMARTSPACE), anyObject(),
-                anyBoolean())
+        verify(listener)
+            .onSmartspaceMediaDataLoaded(eq(KEY_MEDIA_SMARTSPACE), anyObject(), anyBoolean())
 
         // WHEN the media recommendation setting is turned off
-        Settings.Secure.putInt(context.contentResolver,
-                Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0)
+        Settings.Secure.putInt(
+            context.contentResolver,
+            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
+            0
+        )
         tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
 
         // THEN listeners are notified
@@ -665,8 +840,15 @@
         mediaDataManager.setTimedOut(KEY, true, true)
 
         // THEN the last active time is not changed
-        verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), capture(mediaDataCaptor), eq(true),
-                eq(0), eq(false))
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(KEY),
+                eq(KEY),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
         assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime)
     }
 
@@ -687,8 +869,14 @@
 
         // THEN the last active time is not changed
         verify(listener)
-            .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
-                    eq(0), eq(false))
+            .onMediaDataLoaded(
+                eq(PACKAGE_NAME),
+                eq(KEY),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
         assertThat(mediaDataCaptor.value.resumption).isTrue()
         assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime)
 
@@ -700,17 +888,20 @@
     @Test
     fun testTooManyCompactActions_isTruncated() {
         // GIVEN a notification where too many compact actions were specified
-        val notif = SbnBuilder().run {
-            setPkg(PACKAGE_NAME)
-            modifyNotification(context).also {
-                it.setSmallIcon(android.R.drawable.ic_media_pause)
-                it.setStyle(MediaStyle().apply {
-                    setMediaSession(session.sessionToken)
-                    setShowActionsInCompactView(0, 1, 2, 3, 4)
-                })
+        val notif =
+            SbnBuilder().run {
+                setPkg(PACKAGE_NAME)
+                modifyNotification(context).also {
+                    it.setSmallIcon(android.R.drawable.ic_media_pause)
+                    it.setStyle(
+                        MediaStyle().apply {
+                            setMediaSession(session.sessionToken)
+                            setShowActionsInCompactView(0, 1, 2, 3, 4)
+                        }
+                    )
+                }
+                build()
             }
-            build()
-        }
 
         // WHEN the notification is loaded
         mediaDataManager.onNotificationAdded(KEY, notif)
@@ -718,29 +909,35 @@
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
 
         // THEN only the first MAX_COMPACT_ACTIONS are actually set
-        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-                eq(0), eq(false))
-        assertThat(mediaDataCaptor.value.actionsToShowInCompact.size).isEqualTo(
-                MediaDataManager.MAX_COMPACT_ACTIONS)
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(KEY),
+                eq(null),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
+        assertThat(mediaDataCaptor.value.actionsToShowInCompact.size)
+            .isEqualTo(MediaDataManager.MAX_COMPACT_ACTIONS)
     }
 
     @Test
     fun testTooManyNotificationActions_isTruncated() {
         // GIVEN a notification where too many notification actions are added
         val action = Notification.Action(R.drawable.ic_android, "action", null)
-        val notif = SbnBuilder().run {
-            setPkg(PACKAGE_NAME)
-            modifyNotification(context).also {
-                it.setSmallIcon(android.R.drawable.ic_media_pause)
-                it.setStyle(MediaStyle().apply {
-                    setMediaSession(session.sessionToken)
-                })
-                for (i in 0..MediaDataManager.MAX_NOTIFICATION_ACTIONS) {
-                    it.addAction(action)
+        val notif =
+            SbnBuilder().run {
+                setPkg(PACKAGE_NAME)
+                modifyNotification(context).also {
+                    it.setSmallIcon(android.R.drawable.ic_media_pause)
+                    it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+                    for (i in 0..MediaDataManager.MAX_NOTIFICATION_ACTIONS) {
+                        it.addAction(action)
+                    }
                 }
+                build()
             }
-            build()
-        }
 
         // WHEN the notification is loaded
         mediaDataManager.onNotificationAdded(KEY, notif)
@@ -748,10 +945,17 @@
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
 
         // THEN only the first MAX_NOTIFICATION_ACTIONS are actually included
-        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-            eq(0), eq(false))
-        assertThat(mediaDataCaptor.value.actions.size).isEqualTo(
-            MediaDataManager.MAX_NOTIFICATION_ACTIONS)
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(KEY),
+                eq(null),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
+        assertThat(mediaDataCaptor.value.actions.size)
+            .isEqualTo(MediaDataManager.MAX_NOTIFICATION_ACTIONS)
     }
 
     @Test
@@ -760,21 +964,29 @@
         whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         whenever(controller.playbackState).thenReturn(null)
 
-        val notifWithAction = SbnBuilder().run {
-            setPkg(PACKAGE_NAME)
-            modifyNotification(context).also {
-                it.setSmallIcon(android.R.drawable.ic_media_pause)
-                it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
-                it.addAction(android.R.drawable.ic_media_play, desc, null)
+        val notifWithAction =
+            SbnBuilder().run {
+                setPkg(PACKAGE_NAME)
+                modifyNotification(context).also {
+                    it.setSmallIcon(android.R.drawable.ic_media_pause)
+                    it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+                    it.addAction(android.R.drawable.ic_media_play, desc, null)
+                }
+                build()
             }
-            build()
-        }
         mediaDataManager.onNotificationAdded(KEY, notifWithAction)
 
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-                eq(0), eq(false))
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(KEY),
+                eq(null),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
 
         assertThat(mediaDataCaptor.value!!.semanticActions).isNull()
         assertThat(mediaDataCaptor.value!!.actions).hasSize(1)
@@ -785,11 +997,11 @@
     fun testPlaybackActions_hasPrevNext() {
         val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
         whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
-        val stateActions = PlaybackState.ACTION_PLAY or
+        val stateActions =
+            PlaybackState.ACTION_PLAY or
                 PlaybackState.ACTION_SKIP_TO_PREVIOUS or
                 PlaybackState.ACTION_SKIP_TO_NEXT
-        val stateBuilder = PlaybackState.Builder()
-                .setActions(stateActions)
+        val stateBuilder = PlaybackState.Builder().setActions(stateActions)
         customDesc.forEach {
             stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause)
         }
@@ -801,20 +1013,20 @@
         val actions = mediaDataCaptor.value!!.semanticActions!!
 
         assertThat(actions.playOrPause).isNotNull()
-        assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
-                context.getString(R.string.controls_media_button_play))
+        assertThat(actions.playOrPause!!.contentDescription)
+            .isEqualTo(context.getString(R.string.controls_media_button_play))
         actions.playOrPause!!.action!!.run()
         verify(transportControls).play()
 
         assertThat(actions.prevOrCustom).isNotNull()
-        assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo(
-                context.getString(R.string.controls_media_button_prev))
+        assertThat(actions.prevOrCustom!!.contentDescription)
+            .isEqualTo(context.getString(R.string.controls_media_button_prev))
         actions.prevOrCustom!!.action!!.run()
         verify(transportControls).skipToPrevious()
 
         assertThat(actions.nextOrCustom).isNotNull()
-        assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo(
-                context.getString(R.string.controls_media_button_next))
+        assertThat(actions.nextOrCustom!!.contentDescription)
+            .isEqualTo(context.getString(R.string.controls_media_button_next))
         actions.nextOrCustom!!.action!!.run()
         verify(transportControls).skipToNext()
 
@@ -830,8 +1042,7 @@
         val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4", "custom 5")
         whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         val stateActions = PlaybackState.ACTION_PLAY
-        val stateBuilder = PlaybackState.Builder()
-                .setActions(stateActions)
+        val stateBuilder = PlaybackState.Builder().setActions(stateActions)
         customDesc.forEach {
             stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause)
         }
@@ -843,8 +1054,8 @@
         val actions = mediaDataCaptor.value!!.semanticActions!!
 
         assertThat(actions.playOrPause).isNotNull()
-        assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
-                context.getString(R.string.controls_media_button_play))
+        assertThat(actions.playOrPause!!.contentDescription)
+            .isEqualTo(context.getString(R.string.controls_media_button_play))
 
         assertThat(actions.prevOrCustom).isNotNull()
         assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo(customDesc[0])
@@ -863,7 +1074,8 @@
     fun testPlaybackActions_connecting() {
         whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         val stateActions = PlaybackState.ACTION_PLAY
-        val stateBuilder = PlaybackState.Builder()
+        val stateBuilder =
+            PlaybackState.Builder()
                 .setState(PlaybackState.STATE_BUFFERING, 0, 10f)
                 .setActions(stateActions)
         whenever(controller.playbackState).thenReturn(stateBuilder.build())
@@ -874,8 +1086,8 @@
         val actions = mediaDataCaptor.value!!.semanticActions!!
 
         assertThat(actions.playOrPause).isNotNull()
-        assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
-                context.getString(R.string.controls_media_button_connecting))
+        assertThat(actions.playOrPause!!.contentDescription)
+            .isEqualTo(context.getString(R.string.controls_media_button_connecting))
     }
 
     @Test
@@ -883,15 +1095,15 @@
         val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
         whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         val stateActions = PlaybackState.ACTION_PLAY
-        val stateBuilder = PlaybackState.Builder()
-                .setActions(stateActions)
+        val stateBuilder = PlaybackState.Builder().setActions(stateActions)
         customDesc.forEach {
             stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause)
         }
-        val extras = Bundle().apply {
-            putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true)
-            putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true)
-        }
+        val extras =
+            Bundle().apply {
+                putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true)
+                putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true)
+            }
         whenever(controller.playbackState).thenReturn(stateBuilder.build())
         whenever(controller.extras).thenReturn(extras)
 
@@ -901,8 +1113,8 @@
         val actions = mediaDataCaptor.value!!.semanticActions!!
 
         assertThat(actions.playOrPause).isNotNull()
-        assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
-                context.getString(R.string.controls_media_button_play))
+        assertThat(actions.playOrPause!!.contentDescription)
+            .isEqualTo(context.getString(R.string.controls_media_button_play))
 
         assertThat(actions.prevOrCustom).isNull()
         assertThat(actions.nextOrCustom).isNull()
@@ -930,8 +1142,8 @@
         val actions = mediaDataCaptor.value!!.semanticActions!!
 
         assertThat(actions.playOrPause).isNotNull()
-        assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
-            context.getString(R.string.controls_media_button_play))
+        assertThat(actions.playOrPause!!.contentDescription)
+            .isEqualTo(context.getString(R.string.controls_media_button_play))
         actions.playOrPause!!.action!!.run()
         verify(transportControls).play()
     }
@@ -944,30 +1156,43 @@
 
         // Location is updated to local cast
         whenever(controller.metadata).thenReturn(metadataBuilder.build())
-        whenever(playbackInfo.playbackType).thenReturn(
-            MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+        whenever(playbackInfo.playbackType)
+            .thenReturn(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE)
         addNotificationAndLoad()
-        verify(logger).logPlaybackLocationChange(anyInt(), eq(PACKAGE_NAME),
-            eq(instanceId), eq(MediaData.PLAYBACK_CAST_LOCAL))
+        verify(logger)
+            .logPlaybackLocationChange(
+                anyInt(),
+                eq(PACKAGE_NAME),
+                eq(instanceId),
+                eq(MediaData.PLAYBACK_CAST_LOCAL)
+            )
 
         // update to remote cast
-        val rcn = SbnBuilder().run {
-            setPkg(SYSTEM_PACKAGE_NAME) // System package
-            modifyNotification(context).also {
-                it.setSmallIcon(android.R.drawable.ic_media_pause)
-                it.setStyle(MediaStyle().apply {
-                    setMediaSession(session.sessionToken)
-                    setRemotePlaybackInfo("Remote device", 0, null)
-                })
+        val rcn =
+            SbnBuilder().run {
+                setPkg(SYSTEM_PACKAGE_NAME) // System package
+                modifyNotification(context).also {
+                    it.setSmallIcon(android.R.drawable.ic_media_pause)
+                    it.setStyle(
+                        MediaStyle().apply {
+                            setMediaSession(session.sessionToken)
+                            setRemotePlaybackInfo("Remote device", 0, null)
+                        }
+                    )
+                }
+                build()
             }
-            build()
-        }
 
         mediaDataManager.onNotificationAdded(KEY, rcn)
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        verify(logger).logPlaybackLocationChange(anyInt(), eq(SYSTEM_PACKAGE_NAME),
-            eq(instanceId), eq(MediaData.PLAYBACK_CAST_REMOTE))
+        verify(logger)
+            .logPlaybackLocationChange(
+                anyInt(),
+                eq(SYSTEM_PACKAGE_NAME),
+                eq(instanceId),
+                eq(MediaData.PLAYBACK_CAST_REMOTE)
+            )
     }
 
     @Test
@@ -977,14 +1202,19 @@
         verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
 
         // Callback gets an updated state
-        val state = PlaybackState.Builder()
-                .setState(PlaybackState.STATE_PLAYING, 0L, 1f)
-                .build()
+        val state = PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build()
         callbackCaptor.value.invoke(KEY, state)
 
         // Listener is notified of updated state
-        verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY),
-                capture(mediaDataCaptor), eq(true), eq(0), eq(false))
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(KEY),
+                eq(KEY),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
         assertThat(mediaDataCaptor.value.isPlaying).isTrue()
     }
 
@@ -996,8 +1226,8 @@
         // No media added with this key
 
         callbackCaptor.value.invoke(KEY, state)
-        verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(),
-                anyBoolean())
+        verify(listener, never())
+            .onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean())
     }
 
     @Test
@@ -1015,35 +1245,42 @@
 
         // Then no changes are made
         callbackCaptor.value.invoke(KEY, state)
-        verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(),
-            anyBoolean())
+        verify(listener, never())
+            .onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean())
     }
 
     @Test
     fun testPlaybackState_PauseWhenFlagTrue_keyExists_callsListener() {
         whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
-        val state = PlaybackState.Builder()
-                .setState(PlaybackState.STATE_PAUSED, 0L, 1f)
-                .build()
+        val state = PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 1f).build()
         whenever(controller.playbackState).thenReturn(state)
 
         addNotificationAndLoad()
         verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
         callbackCaptor.value.invoke(KEY, state)
 
-        verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY),
-                capture(mediaDataCaptor), eq(true), eq(0), eq(false))
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(KEY),
+                eq(KEY),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
         assertThat(mediaDataCaptor.value.isPlaying).isFalse()
         assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
     }
 
     @Test
     fun testPlaybackState_PauseStateAfterAddingResumption_keyExists_callsListener() {
-        val desc = MediaDescription.Builder().run {
-            setTitle(SESSION_TITLE)
-            build()
-        }
-        val state = PlaybackState.Builder()
+        val desc =
+            MediaDescription.Builder().run {
+                setTitle(SESSION_TITLE)
+                build()
+            }
+        val state =
+            PlaybackState.Builder()
                 .setState(PlaybackState.STATE_PAUSED, 0L, 1f)
                 .setActions(PlaybackState.ACTION_PLAY_PAUSE)
                 .build()
@@ -1051,13 +1288,13 @@
         // Add resumption controls in order to have semantic actions.
         // To make sure that they are not null after changing state.
         mediaDataManager.addResumptionControls(
-                USER_ID,
-                desc,
-                Runnable {},
-                session.sessionToken,
-                APP_NAME,
-                pendingIntent,
-                PACKAGE_NAME
+            USER_ID,
+            desc,
+            Runnable {},
+            session.sessionToken,
+            APP_NAME,
+            pendingIntent,
+            PACKAGE_NAME
         )
         backgroundExecutor.runAllReady()
         foregroundExecutor.runAllReady()
@@ -1066,14 +1303,14 @@
         callbackCaptor.value.invoke(PACKAGE_NAME, state)
 
         verify(listener)
-                .onMediaDataLoaded(
-                        eq(PACKAGE_NAME),
-                        eq(PACKAGE_NAME),
-                        capture(mediaDataCaptor),
-                        eq(true),
-                        eq(0),
-                        eq(false)
-                )
+            .onMediaDataLoaded(
+                eq(PACKAGE_NAME),
+                eq(PACKAGE_NAME),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
         assertThat(mediaDataCaptor.value.isPlaying).isFalse()
         assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
     }
@@ -1081,7 +1318,8 @@
     @Test
     fun testPlaybackStateNull_Pause_keyExists_callsListener() {
         whenever(controller.playbackState).thenReturn(null)
-        val state = PlaybackState.Builder()
+        val state =
+            PlaybackState.Builder()
                 .setState(PlaybackState.STATE_PAUSED, 0L, 1f)
                 .setActions(PlaybackState.ACTION_PLAY_PAUSE)
                 .build()
@@ -1090,20 +1328,32 @@
         verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
         callbackCaptor.value.invoke(KEY, state)
 
-        verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY),
-                capture(mediaDataCaptor), eq(true), eq(0), eq(false))
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(KEY),
+                eq(KEY),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
         assertThat(mediaDataCaptor.value.isPlaying).isFalse()
         assertThat(mediaDataCaptor.value.semanticActions).isNull()
     }
 
-    /**
-     * Helper function to add a media notification and capture the resulting MediaData
-     */
+    /** Helper function to add a media notification and capture the resulting MediaData */
     private fun addNotificationAndLoad() {
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-            eq(0), eq(false))
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(KEY),
+                eq(null),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
similarity index 93%
rename from packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
index 121c894..a45e9d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.pipeline
 
 import android.bluetooth.BluetoothLeBroadcast
 import android.bluetooth.BluetoothLeBroadcastMetadata
@@ -37,6 +37,10 @@
 import com.android.systemui.R
 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.util.MediaControllerFactory
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -109,7 +113,8 @@
         fakeFgExecutor = FakeExecutor(FakeSystemClock())
         fakeBgExecutor = FakeExecutor(FakeSystemClock())
         localBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager::class.java)
-        manager = MediaDeviceManager(
+        manager =
+            MediaDeviceManager(
                 context,
                 controllerFactory,
                 lmmFactory,
@@ -120,7 +125,7 @@
                 fakeFgExecutor,
                 fakeBgExecutor,
                 dumpster
-        )
+            )
         manager.addListener(listener)
 
         // Configure mocks.
@@ -134,11 +139,9 @@
         // Create a media sesssion and notification for testing.
         session = MediaSession(context, SESSION_KEY)
 
-        mediaData = MediaTestUtils.emptyMediaData.copy(
-                packageName = PACKAGE,
-                token = session.sessionToken)
-        whenever(controllerFactory.create(session.sessionToken))
-                .thenReturn(controller)
+        mediaData =
+            MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, token = session.sessionToken)
+        whenever(controllerFactory.create(session.sessionToken)).thenReturn(controller)
         setupLeAudioConfiguration(false)
     }
 
@@ -354,7 +357,9 @@
         val deviceCallback = captureCallback()
         // First set a non-null about-to-connect device
         deviceCallback.onAboutToConnectDeviceAdded(
-            "fakeAddress", "AboutToConnectDeviceName", mock(Drawable::class.java)
+            "fakeAddress",
+            "AboutToConnectDeviceName",
+            mock(Drawable::class.java)
         )
         // Run and reset the executors and listeners so we only focus on new events.
         fakeBgExecutor.runAllReady()
@@ -583,8 +588,8 @@
     @Test
     fun testRemotePlaybackDeviceOverride() {
         whenever(route.name).thenReturn(DEVICE_NAME)
-        val deviceData = MediaDeviceData(false, null, REMOTE_DEVICE_NAME, null,
-                showBroadcastButton = false)
+        val deviceData =
+            MediaDeviceData(false, null, REMOTE_DEVICE_NAME, null, showBroadcastButton = false)
         val mediaDataWithDevice = mediaData.copy(device = deviceData)
 
         // GIVEN media data that already has a device set
@@ -613,8 +618,8 @@
         val data = captureDeviceData(KEY)
         assertThat(data.showBroadcastButton).isTrue()
         assertThat(data.enabled).isTrue()
-        assertThat(data.name).isEqualTo(context.getString(
-                R.string.broadcasting_description_is_broadcasting))
+        assertThat(data.name)
+            .isEqualTo(context.getString(R.string.broadcasting_description_is_broadcasting))
     }
 
     @Test
@@ -655,20 +660,21 @@
     }
 
     fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback {
-        val callback: BluetoothLeBroadcast.Callback = object : BluetoothLeBroadcast.Callback {
-            override fun onBroadcastStarted(reason: Int, broadcastId: Int) {}
-            override fun onBroadcastStartFailed(reason: Int) {}
-            override fun onBroadcastStopped(reason: Int, broadcastId: Int) {}
-            override fun onBroadcastStopFailed(reason: Int) {}
-            override fun onPlaybackStarted(reason: Int, broadcastId: Int) {}
-            override fun onPlaybackStopped(reason: Int, broadcastId: Int) {}
-            override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {}
-            override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {}
-            override fun onBroadcastMetadataChanged(
-                broadcastId: Int,
-                metadata: BluetoothLeBroadcastMetadata
-            ) {}
-        }
+        val callback: BluetoothLeBroadcast.Callback =
+            object : BluetoothLeBroadcast.Callback {
+                override fun onBroadcastStarted(reason: Int, broadcastId: Int) {}
+                override fun onBroadcastStartFailed(reason: Int) {}
+                override fun onBroadcastStopped(reason: Int, broadcastId: Int) {}
+                override fun onBroadcastStopFailed(reason: Int) {}
+                override fun onPlaybackStarted(reason: Int, broadcastId: Int) {}
+                override fun onPlaybackStopped(reason: Int, broadcastId: Int) {}
+                override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {}
+                override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {}
+                override fun onBroadcastMetadataChanged(
+                    broadcastId: Int,
+                    metadata: BluetoothLeBroadcastMetadata
+                ) {}
+            }
 
         bluetoothLeBroadcast.registerCallback(fakeFgExecutor, callback)
         return callback
@@ -677,7 +683,7 @@
     fun setupLeAudioConfiguration(isLeAudio: Boolean) {
         whenever(localBluetoothManager.profileManager).thenReturn(localBluetoothProfileManager)
         whenever(localBluetoothProfileManager.leAudioBroadcastProfile)
-                .thenReturn(localBluetoothLeBroadcast)
+            .thenReturn(localBluetoothLeBroadcast)
         whenever(localBluetoothLeBroadcast.isEnabled(any())).thenReturn(isLeAudio)
         whenever(localBluetoothLeBroadcast.appSourceName).thenReturn(BROADCAST_APP_NAME)
     }
@@ -685,7 +691,7 @@
     fun setupBroadcastPackage(currentName: String) {
         whenever(lmm.packageName).thenReturn(PACKAGE)
         whenever(packageManager.getApplicationInfo(eq(PACKAGE), anyInt()))
-                .thenReturn(applicationInfo)
+            .thenReturn(applicationInfo)
         whenever(packageManager.getApplicationLabel(applicationInfo)).thenReturn(currentName)
         context.setMockPackageManager(packageManager)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilterTest.kt
similarity index 84%
rename from packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilterTest.kt
index 5586453..3099609 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilterTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.pipeline
 
 import android.media.session.MediaController
 import android.media.session.MediaController.PlaybackInfo
@@ -23,12 +23,12 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 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.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
-
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
@@ -42,17 +42,15 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
 
 private const val PACKAGE = "PKG"
 private const val KEY = "TEST_KEY"
 private const val NOTIF_KEY = "TEST_KEY"
 
-private val info = MediaTestUtils.emptyMediaData.copy(
-    packageName = PACKAGE,
-    notificationKey = NOTIF_KEY
-)
+private val info =
+    MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, notificationKey = NOTIF_KEY)
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -139,10 +137,10 @@
 
         // Capture listener
         bgExecutor.runAllReady()
-        val listenerCaptor = ArgumentCaptor.forClass(
-                MediaSessionManager.OnActiveSessionsChangedListener::class.java)
-        verify(mediaSessionManager).addOnActiveSessionsChangedListener(
-                listenerCaptor.capture(), any())
+        val listenerCaptor =
+            ArgumentCaptor.forClass(MediaSessionManager.OnActiveSessionsChangedListener::class.java)
+        verify(mediaSessionManager)
+            .addOnActiveSessionsChangedListener(listenerCaptor.capture(), any())
         sessionListener = listenerCaptor.value
 
         filter.addListener(mediaListener)
@@ -161,8 +159,8 @@
         filter.onMediaDataLoaded(KEY, null, mediaData1)
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
-        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
-                eq(0), eq(false))
+        verify(mediaListener)
+            .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
     }
 
     @Test
@@ -184,8 +182,8 @@
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the event is not filtered
-        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
-                eq(0), eq(false))
+        verify(mediaListener)
+            .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
     }
 
     @Test
@@ -214,8 +212,8 @@
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the event is not filtered
-        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
-                eq(0), eq(false))
+        verify(mediaListener)
+            .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
     }
 
     @Test
@@ -230,15 +228,22 @@
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the event is not filtered
-        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
-                eq(0), eq(false))
+        verify(mediaListener)
+            .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
         // WHEN a loaded event is received that matches the local session
         filter.onMediaDataLoaded(KEY, null, mediaData2)
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the event is filtered
-        verify(mediaListener, never()).onMediaDataLoaded(
-            eq(KEY), eq(null), eq(mediaData2), anyBoolean(), anyInt(), anyBoolean())
+        verify(mediaListener, never())
+            .onMediaDataLoaded(
+                eq(KEY),
+                eq(null),
+                eq(mediaData2),
+                anyBoolean(),
+                anyInt(),
+                anyBoolean()
+            )
     }
 
     @Test
@@ -254,8 +259,8 @@
         fgExecutor.runAllReady()
         // THEN the event is not filtered because there isn't a notification for the remote
         // session.
-        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
-                eq(0), eq(false))
+        verify(mediaListener)
+            .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
     }
 
     @Test
@@ -272,16 +277,22 @@
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the event is not filtered
-        verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true),
-                eq(0), eq(false))
+        verify(mediaListener)
+            .onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
         // WHEN a loaded event is received that matches the local session
         filter.onMediaDataLoaded(key2, null, mediaData2)
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the event is filtered
         verify(mediaListener, never())
-            .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), anyBoolean(),
-                    anyInt(), anyBoolean())
+            .onMediaDataLoaded(
+                eq(key2),
+                eq(null),
+                eq(mediaData2),
+                anyBoolean(),
+                anyInt(),
+                anyBoolean()
+            )
         // AND there should be a removed event for key2
         verify(mediaListener).onMediaDataRemoved(eq(key2))
     }
@@ -300,15 +311,15 @@
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the event is not filtered
-        verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true),
-                eq(0), eq(false))
+        verify(mediaListener)
+            .onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
         // WHEN a loaded event is received that matches the remote session
         filter.onMediaDataLoaded(key2, null, mediaData2)
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the event is not filtered
-        verify(mediaListener).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), eq(true),
-                eq(0), eq(false))
+        verify(mediaListener)
+            .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), eq(true), eq(0), eq(false))
     }
 
     @Test
@@ -324,15 +335,15 @@
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the event is not filtered
-        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
-                eq(0), eq(false))
+        verify(mediaListener)
+            .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
         // WHEN a loaded event is received that matches the local session
         filter.onMediaDataLoaded(KEY, null, mediaData2)
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the event is not filtered
-        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2), eq(true),
-                eq(0), eq(false))
+        verify(mediaListener)
+            .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2), eq(true), eq(0), eq(false))
     }
 
     @Test
@@ -350,8 +361,8 @@
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the event is not filtered
-        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
-                eq(0), eq(false))
+        verify(mediaListener)
+            .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
     }
 
     @Test
@@ -373,8 +384,8 @@
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the key migration event is fired
-        verify(mediaListener).onMediaDataLoaded(eq(key2), eq(key1), eq(mediaData2), eq(true),
-                eq(0), eq(false))
+        verify(mediaListener)
+            .onMediaDataLoaded(eq(key2), eq(key1), eq(mediaData2), eq(true), eq(0), eq(false))
     }
 
     @Test
@@ -404,14 +415,20 @@
         fgExecutor.runAllReady()
         // THEN the key migration event is filtered
         verify(mediaListener, never())
-            .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), anyBoolean(),
-                    anyInt(), anyBoolean())
+            .onMediaDataLoaded(
+                eq(key2),
+                eq(null),
+                eq(mediaData2),
+                anyBoolean(),
+                anyInt(),
+                anyBoolean()
+            )
         // WHEN a loaded event is received that matches the remote session
         filter.onMediaDataLoaded(key2, null, mediaData1)
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the key migration event is fired
-        verify(mediaListener).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData1), eq(true),
-                eq(0), eq(false))
+        verify(mediaListener)
+            .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt
similarity index 81%
rename from packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt
index 823d4ae..344dffa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.pipeline
 
 import android.media.MediaMetadata
 import android.media.session.MediaController
@@ -23,6 +23,9 @@
 import android.testing.AndroidTestingRunner
 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.util.MediaControllerFactory
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -41,11 +44,11 @@
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.junit.MockitoJUnit
 
 private const val KEY = "KEY"
@@ -70,7 +73,8 @@
     @Mock private lateinit var timeoutCallback: (String, Boolean) -> Unit
     @Mock private lateinit var stateCallback: (String, PlaybackState) -> Unit
     @Captor private lateinit var mediaCallbackCaptor: ArgumentCaptor<MediaController.Callback>
-    @Captor private lateinit var dozingCallbackCaptor:
+    @Captor
+    private lateinit var dozingCallbackCaptor:
         ArgumentCaptor<StatusBarStateController.StateListener>
     @JvmField @Rule val mockito = MockitoJUnit.rule()
     private lateinit var metadataBuilder: MediaMetadata.Builder
@@ -85,36 +89,41 @@
     fun setup() {
         `when`(mediaControllerFactory.create(any())).thenReturn(mediaController)
         executor = FakeExecutor(clock)
-        mediaTimeoutListener = MediaTimeoutListener(
-            mediaControllerFactory,
-            executor,
-            logger,
-            statusBarStateController,
-            clock
-        )
+        mediaTimeoutListener =
+            MediaTimeoutListener(
+                mediaControllerFactory,
+                executor,
+                logger,
+                statusBarStateController,
+                clock
+            )
         mediaTimeoutListener.timeoutCallback = timeoutCallback
         mediaTimeoutListener.stateCallback = stateCallback
 
         // Create a media session and notification for testing.
-        metadataBuilder = MediaMetadata.Builder().apply {
-            putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
-            putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
-        }
-        playbackBuilder = PlaybackState.Builder().apply {
-            setState(PlaybackState.STATE_PAUSED, 6000L, 1f)
-            setActions(PlaybackState.ACTION_PLAY)
-        }
-        session = MediaSession(context, SESSION_KEY).apply {
-            setMetadata(metadataBuilder.build())
-            setPlaybackState(playbackBuilder.build())
-        }
+        metadataBuilder =
+            MediaMetadata.Builder().apply {
+                putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
+                putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
+            }
+        playbackBuilder =
+            PlaybackState.Builder().apply {
+                setState(PlaybackState.STATE_PAUSED, 6000L, 1f)
+                setActions(PlaybackState.ACTION_PLAY)
+            }
+        session =
+            MediaSession(context, SESSION_KEY).apply {
+                setMetadata(metadataBuilder.build())
+                setPlaybackState(playbackBuilder.build())
+            }
         session.setActive(true)
 
-        mediaData = MediaTestUtils.emptyMediaData.copy(
-            app = PACKAGE,
-            packageName = PACKAGE,
-            token = session.sessionToken
-        )
+        mediaData =
+            MediaTestUtils.emptyMediaData.copy(
+                app = PACKAGE,
+                packageName = PACKAGE,
+                token = session.sessionToken
+            )
 
         resumeData = mediaData.copy(token = null, active = false, resumption = true)
     }
@@ -212,8 +221,9 @@
         // Assuming we're registered
         testOnMediaDataLoaded_registersPlaybackListener()
 
-        mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
-                .setState(PlaybackState.STATE_PAUSED, 0L, 0f).build())
+        mediaCallbackCaptor.value.onPlaybackStateChanged(
+            PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
+        )
         assertThat(executor.numPending()).isEqualTo(1)
         assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT)
     }
@@ -223,8 +233,9 @@
         // Assuming we have a pending timeout
         testOnPlaybackStateChanged_schedulesTimeout_whenPaused()
 
-        mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
-                .setState(PlaybackState.STATE_PLAYING, 0L, 0f).build())
+        mediaCallbackCaptor.value.onPlaybackStateChanged(
+            PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 0f).build()
+        )
         assertThat(executor.numPending()).isEqualTo(0)
         verify(logger).logTimeoutCancelled(eq(KEY), any())
     }
@@ -234,8 +245,9 @@
         // Assuming we have a pending timeout
         testOnPlaybackStateChanged_schedulesTimeout_whenPaused()
 
-        mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
-                .setState(PlaybackState.STATE_STOPPED, 0L, 0f).build())
+        mediaCallbackCaptor.value.onPlaybackStateChanged(
+            PlaybackState.Builder().setState(PlaybackState.STATE_STOPPED, 0L, 0f).build()
+        )
         assertThat(executor.numPending()).isEqualTo(1)
     }
 
@@ -329,9 +341,8 @@
     @Test
     fun testOnMediaDataLoaded_pausedToResume_updatesTimeout() {
         // WHEN regular media is paused
-        val pausedState = PlaybackState.Builder()
-                .setState(PlaybackState.STATE_PAUSED, 0L, 0f)
-                .build()
+        val pausedState =
+            PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
         `when`(mediaController.playbackState).thenReturn(pausedState)
         mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
         assertThat(executor.numPending()).isEqualTo(1)
@@ -362,9 +373,8 @@
         mediaTimeoutListener.onMediaDataLoaded(PACKAGE, null, resumeData)
 
         // AND that media is resumed
-        val playingState = PlaybackState.Builder()
-                .setState(PlaybackState.STATE_PAUSED, 0L, 0f)
-                .build()
+        val playingState =
+            PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
         `when`(mediaController.playbackState).thenReturn(playingState)
         mediaTimeoutListener.onMediaDataLoaded(KEY, PACKAGE, mediaData)
 
@@ -386,15 +396,11 @@
     @Test
     fun testOnMediaDataLoaded_playbackActionsChanged_noCallback() {
         // Load media data once
-        val pausedState = PlaybackState.Builder()
-                .setActions(PlaybackState.ACTION_PAUSE)
-                .build()
+        val pausedState = PlaybackState.Builder().setActions(PlaybackState.ACTION_PAUSE).build()
         loadMediaDataWithPlaybackState(pausedState)
 
         // When media data is loaded again, with different actions
-        val playingState = PlaybackState.Builder()
-                .setActions(PlaybackState.ACTION_PLAY)
-                .build()
+        val playingState = PlaybackState.Builder().setActions(PlaybackState.ACTION_PLAY).build()
         loadMediaDataWithPlaybackState(playingState)
 
         // Then the callback is not invoked
@@ -404,15 +410,11 @@
     @Test
     fun testOnPlaybackStateChanged_playbackActionsChanged_sendsCallback() {
         // Load media data once
-        val pausedState = PlaybackState.Builder()
-                .setActions(PlaybackState.ACTION_PAUSE)
-                .build()
+        val pausedState = PlaybackState.Builder().setActions(PlaybackState.ACTION_PAUSE).build()
         loadMediaDataWithPlaybackState(pausedState)
 
         // When the playback state changes, and has different actions
-        val playingState = PlaybackState.Builder()
-                .setActions(PlaybackState.ACTION_PLAY)
-                .build()
+        val playingState = PlaybackState.Builder().setActions(PlaybackState.ACTION_PLAY).build()
         mediaCallbackCaptor.value.onPlaybackStateChanged(playingState)
 
         // Then the callback is invoked
@@ -421,24 +423,30 @@
 
     @Test
     fun testOnPlaybackStateChanged_differentCustomActions_sendsCallback() {
-        val customOne = PlaybackState.CustomAction.Builder(
+        val customOne =
+            PlaybackState.CustomAction.Builder(
                     "ACTION_1",
                     "custom action 1",
-                    android.R.drawable.ic_media_ff)
+                    android.R.drawable.ic_media_ff
+                )
                 .build()
-        val pausedState = PlaybackState.Builder()
+        val pausedState =
+            PlaybackState.Builder()
                 .setActions(PlaybackState.ACTION_PAUSE)
                 .addCustomAction(customOne)
                 .build()
         loadMediaDataWithPlaybackState(pausedState)
 
         // When the playback state actions change
-        val customTwo = PlaybackState.CustomAction.Builder(
-                "ACTION_2",
-                "custom action 2",
-                android.R.drawable.ic_media_rew)
+        val customTwo =
+            PlaybackState.CustomAction.Builder(
+                    "ACTION_2",
+                    "custom action 2",
+                    android.R.drawable.ic_media_rew
+                )
                 .build()
-        val pausedStateTwoActions = PlaybackState.Builder()
+        val pausedStateTwoActions =
+            PlaybackState.Builder()
                 .setActions(PlaybackState.ACTION_PAUSE)
                 .addCustomAction(customOne)
                 .addCustomAction(customTwo)
@@ -451,9 +459,7 @@
 
     @Test
     fun testOnPlaybackStateChanged_sameActions_noCallback() {
-        val stateWithActions = PlaybackState.Builder()
-                .setActions(PlaybackState.ACTION_PLAY)
-                .build()
+        val stateWithActions = PlaybackState.Builder().setActions(PlaybackState.ACTION_PLAY).build()
         loadMediaDataWithPlaybackState(stateWithActions)
 
         // When the playback state updates with the same actions
@@ -467,18 +473,20 @@
     fun testOnPlaybackStateChanged_sameCustomActions_noCallback() {
         val actionName = "custom action"
         val actionIcon = android.R.drawable.ic_media_ff
-        val customOne = PlaybackState.CustomAction.Builder(actionName, actionName, actionIcon)
-                .build()
-        val stateOne = PlaybackState.Builder()
+        val customOne =
+            PlaybackState.CustomAction.Builder(actionName, actionName, actionIcon).build()
+        val stateOne =
+            PlaybackState.Builder()
                 .setActions(PlaybackState.ACTION_PAUSE)
                 .addCustomAction(customOne)
                 .build()
         loadMediaDataWithPlaybackState(stateOne)
 
         // When the playback state is updated, but has the same actions
-        val customTwo = PlaybackState.CustomAction.Builder(actionName, actionName, actionIcon)
-                .build()
-        val stateTwo = PlaybackState.Builder()
+        val customTwo =
+            PlaybackState.CustomAction.Builder(actionName, actionName, actionIcon).build()
+        val stateTwo =
+            PlaybackState.Builder()
                 .setActions(PlaybackState.ACTION_PAUSE)
                 .addCustomAction(customTwo)
                 .build()
@@ -491,15 +499,13 @@
     @Test
     fun testOnMediaDataLoaded_isPlayingChanged_noCallback() {
         // Load media data in paused state
-        val pausedState = PlaybackState.Builder()
-                .setState(PlaybackState.STATE_PAUSED, 0L, 0f)
-                .build()
+        val pausedState =
+            PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
         loadMediaDataWithPlaybackState(pausedState)
 
         // When media data is loaded again but playing
-        val playingState = PlaybackState.Builder()
-                .setState(PlaybackState.STATE_PLAYING, 0L, 1f)
-                .build()
+        val playingState =
+            PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build()
         loadMediaDataWithPlaybackState(playingState)
 
         // Then the callback is not invoked
@@ -509,15 +515,13 @@
     @Test
     fun testOnPlaybackStateChanged_isPlayingChanged_sendsCallback() {
         // Load media data in paused state
-        val pausedState = PlaybackState.Builder()
-                .setState(PlaybackState.STATE_PAUSED, 0L, 0f)
-                .build()
+        val pausedState =
+            PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
         loadMediaDataWithPlaybackState(pausedState)
 
         // When the playback state changes to playing
-        val playingState = PlaybackState.Builder()
-                .setState(PlaybackState.STATE_PLAYING, 0L, 1f)
-                .build()
+        val playingState =
+            PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build()
         mediaCallbackCaptor.value.onPlaybackStateChanged(playingState)
 
         // Then the callback is invoked
@@ -527,15 +531,13 @@
     @Test
     fun testOnPlaybackStateChanged_isPlayingSame_noCallback() {
         // Load media data in paused state
-        val pausedState = PlaybackState.Builder()
-                .setState(PlaybackState.STATE_PAUSED, 0L, 0f)
-                .build()
+        val pausedState =
+            PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
         loadMediaDataWithPlaybackState(pausedState)
 
         // When the playback state is updated, but still not playing
-        val playingState = PlaybackState.Builder()
-                .setState(PlaybackState.STATE_STOPPED, 0L, 0f)
-                .build()
+        val playingState =
+            PlaybackState.Builder().setState(PlaybackState.STATE_STOPPED, 0L, 0f).build()
         mediaCallbackCaptor.value.onPlaybackStateChanged(playingState)
 
         // Then the callback is not invoked
@@ -546,8 +548,9 @@
     fun testTimeoutCallback_dozedPastTimeout_invokedOnWakeup() {
         // When paused media is loaded
         testOnMediaDataLoaded_registersPlaybackListener()
-        mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
-            .setState(PlaybackState.STATE_PAUSED, 0L, 0f).build())
+        mediaCallbackCaptor.value.onPlaybackStateChanged(
+            PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
+        )
         verify(statusBarStateController).addCallback(capture(dozingCallbackCaptor))
 
         // And we doze past the scheduled timeout
@@ -571,8 +574,9 @@
         val time = clock.currentTimeMillis()
         clock.setElapsedRealtime(time)
         testOnMediaDataLoaded_registersPlaybackListener()
-        mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
-            .setState(PlaybackState.STATE_PAUSED, 0L, 0f).build())
+        mediaCallbackCaptor.value.onPlaybackStateChanged(
+            PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
+        )
         verify(statusBarStateController).addCallback(capture(dozingCallbackCaptor))
 
         // And we doze, but not past the scheduled timeout
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
similarity index 78%
rename from packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
index 3d3ac83..84fdfd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.resume
 
 import android.app.PendingIntent
 import android.content.ComponentName
@@ -33,11 +33,16 @@
 import com.android.systemui.SysuiTestCase
 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.tuner.TunerService
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
-import org.junit.After
 import com.google.common.truth.Truth.assertThat
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -63,7 +68,9 @@
 private const val RESUME_COMPONENTS = "package1/class1:package2/class2:package3/class3"
 
 private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+
 private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+
 private fun <T> any(): T = Mockito.any<T>()
 
 @SmallTest
@@ -93,26 +100,32 @@
     private lateinit var resumeListener: MediaResumeListener
     private val clock = FakeSystemClock()
 
-    private var originalQsSetting = Settings.Global.getInt(context.contentResolver,
-        Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1)
-    private var originalResumeSetting = Settings.Secure.getInt(context.contentResolver,
-        Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
+    private var originalQsSetting =
+        Settings.Global.getInt(
+            context.contentResolver,
+            Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS,
+            1
+        )
+    private var originalResumeSetting =
+        Settings.Secure.getInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
 
-        Settings.Global.putInt(context.contentResolver,
-            Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1)
-        Settings.Secure.putInt(context.contentResolver,
-            Settings.Secure.MEDIA_CONTROLS_RESUME, 1)
+        Settings.Global.putInt(
+            context.contentResolver,
+            Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS,
+            1
+        )
+        Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 1)
 
         whenever(resumeBrowserFactory.create(capture(callbackCaptor), any()))
-                .thenReturn(resumeBrowser)
+            .thenReturn(resumeBrowser)
 
         // resume components are stored in sharedpreferences
         whenever(mockContext.getSharedPreferences(eq(MEDIA_PREFERENCES), anyInt()))
-                .thenReturn(sharedPrefs)
+            .thenReturn(sharedPrefs)
         whenever(sharedPrefs.getString(any(), any())).thenReturn(RESUME_COMPONENTS)
         whenever(sharedPrefs.edit()).thenReturn(sharedPrefsEditor)
         whenever(sharedPrefsEditor.putString(any(), any())).thenReturn(sharedPrefsEditor)
@@ -120,36 +133,59 @@
         whenever(mockContext.contentResolver).thenReturn(context.contentResolver)
 
         executor = FakeExecutor(clock)
-        resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
-                tunerService, resumeBrowserFactory, dumpManager, clock)
+        resumeListener =
+            MediaResumeListener(
+                mockContext,
+                broadcastDispatcher,
+                executor,
+                tunerService,
+                resumeBrowserFactory,
+                dumpManager,
+                clock
+            )
         resumeListener.setManager(mediaDataManager)
         mediaDataManager.addListener(resumeListener)
 
-        data = MediaTestUtils.emptyMediaData.copy(
+        data =
+            MediaTestUtils.emptyMediaData.copy(
                 song = TITLE,
                 packageName = PACKAGE_NAME,
-                token = token)
+                token = token
+            )
     }
 
     @After
     fun tearDown() {
-        Settings.Global.putInt(context.contentResolver,
-            Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, originalQsSetting)
-        Settings.Secure.putInt(context.contentResolver,
-            Settings.Secure.MEDIA_CONTROLS_RESUME, originalResumeSetting)
+        Settings.Global.putInt(
+            context.contentResolver,
+            Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS,
+            originalQsSetting
+        )
+        Settings.Secure.putInt(
+            context.contentResolver,
+            Settings.Secure.MEDIA_CONTROLS_RESUME,
+            originalResumeSetting
+        )
     }
 
     @Test
     fun testWhenNoResumption_doesNothing() {
-        Settings.Secure.putInt(context.contentResolver,
-            Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
+        Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
 
         // When listener is created, we do NOT register a user change listener
-        val listener = MediaResumeListener(context, broadcastDispatcher, executor, tunerService,
-                resumeBrowserFactory, dumpManager, clock)
+        val listener =
+            MediaResumeListener(
+                context,
+                broadcastDispatcher,
+                executor,
+                tunerService,
+                resumeBrowserFactory,
+                dumpManager,
+                clock
+            )
         listener.setManager(mediaDataManager)
-        verify(broadcastDispatcher, never()).registerReceiver(eq(listener.userChangeReceiver),
-            any(), any(), any(), anyInt(), any())
+        verify(broadcastDispatcher, never())
+            .registerReceiver(eq(listener.userChangeReceiver), any(), any(), any(), anyInt(), any())
 
         // When data is loaded, we do NOT execute or update anything
         listener.onMediaDataLoaded(KEY, OLD_KEY, data)
@@ -170,9 +206,7 @@
     fun testOnLoad_checksForResume_badService() {
         setUpMbsWithValidResolveInfo()
 
-        whenever(resumeBrowser.testConnection()).thenAnswer {
-            callbackCaptor.value.onError()
-        }
+        whenever(resumeBrowser.testConnection()).thenAnswer { callbackCaptor.value.onError() }
 
         // When media data is loaded that has not been checked yet, and does not have a MBS
         resumeListener.onMediaDataLoaded(KEY, null, data)
@@ -226,7 +260,7 @@
 
         // But we do not tell it to add new controls
         verify(mediaDataManager, never())
-                .addResumptionControls(anyInt(), any(), any(), any(), any(), any(), any())
+            .addResumptionControls(anyInt(), any(), any(), any(), any(), any(), any())
     }
 
     @Test
@@ -253,8 +287,15 @@
 
         // Make sure broadcast receiver is registered
         resumeListener.setManager(mediaDataManager)
-        verify(broadcastDispatcher).registerReceiver(eq(resumeListener.userChangeReceiver),
-                any(), any(), any(), anyInt(), any())
+        verify(broadcastDispatcher)
+            .registerReceiver(
+                eq(resumeListener.userChangeReceiver),
+                any(),
+                any(),
+                any(),
+                anyInt(),
+                any()
+            )
 
         // When we get an unlock event
         val intent = Intent(Intent.ACTION_USER_UNLOCKED)
@@ -264,8 +305,8 @@
         verify(resumeBrowser, times(3)).findRecentMedia()
 
         // Then since the mock service found media, the manager should be informed
-        verify(mediaDataManager, times(3)).addResumptionControls(anyInt(),
-                any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
+        verify(mediaDataManager, times(3))
+            .addResumptionControls(anyInt(), any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
     }
 
     @Test
@@ -304,12 +345,14 @@
 
         // Then we save an update with the current time
         verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor)))
-        componentCaptor.value.split(ResumeMediaBrowser.DELIMITER.toRegex())
-                ?.dropLastWhile { it.isEmpty() }.forEach {
-            val result = it.split("/")
-            assertThat(result.size).isEqualTo(3)
-            assertThat(result[2].toLong()).isEqualTo(currentTime)
-        }
+        componentCaptor.value
+            .split(ResumeMediaBrowser.DELIMITER.toRegex())
+            .dropLastWhile { it.isEmpty() }
+            .forEach {
+                val result = it.split("/")
+                assertThat(result.size).isEqualTo(3)
+                assertThat(result[2].toLong()).isEqualTo(currentTime)
+            }
         verify(sharedPrefsEditor, times(1)).apply()
     }
 
@@ -328,8 +371,16 @@
         val lastPlayed = clock.currentTimeMillis()
         val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
         whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
-        val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
-                tunerService, resumeBrowserFactory, dumpManager, clock)
+        val resumeListener =
+            MediaResumeListener(
+                mockContext,
+                broadcastDispatcher,
+                executor,
+                tunerService,
+                resumeBrowserFactory,
+                dumpManager,
+                clock
+            )
         resumeListener.setManager(mediaDataManager)
         mediaDataManager.addListener(resumeListener)
 
@@ -339,8 +390,8 @@
 
         // We add its resume controls
         verify(resumeBrowser, times(1)).findRecentMedia()
-        verify(mediaDataManager, times(1)).addResumptionControls(anyInt(),
-                any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
+        verify(mediaDataManager, times(1))
+            .addResumptionControls(anyInt(), any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
     }
 
     @Test
@@ -349,8 +400,16 @@
         val lastPlayed = clock.currentTimeMillis() - RESUME_MEDIA_TIMEOUT - 100
         val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
         whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
-        val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
-                tunerService, resumeBrowserFactory, dumpManager, clock)
+        val resumeListener =
+            MediaResumeListener(
+                mockContext,
+                broadcastDispatcher,
+                executor,
+                tunerService,
+                resumeBrowserFactory,
+                dumpManager,
+                clock
+            )
         resumeListener.setManager(mediaDataManager)
         mediaDataManager.addListener(resumeListener)
 
@@ -360,8 +419,8 @@
 
         // We do not try to add resume controls
         verify(resumeBrowser, times(0)).findRecentMedia()
-        verify(mediaDataManager, times(0)).addResumptionControls(anyInt(),
-                any(), any(), any(), any(), any(), any())
+        verify(mediaDataManager, times(0))
+            .addResumptionControls(anyInt(), any(), any(), any(), any(), any(), any())
     }
 
     @Test
@@ -380,8 +439,16 @@
         val lastPlayed = currentTime - 1000
         val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
         whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
-        val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
-                tunerService, resumeBrowserFactory, dumpManager, clock)
+        val resumeListener =
+            MediaResumeListener(
+                mockContext,
+                broadcastDispatcher,
+                executor,
+                tunerService,
+                resumeBrowserFactory,
+                dumpManager,
+                clock
+            )
         resumeListener.setManager(mediaDataManager)
         mediaDataManager.addListener(resumeListener)
 
@@ -391,12 +458,14 @@
 
         // Then we store the new lastPlayed time
         verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor)))
-        componentCaptor.value.split(ResumeMediaBrowser.DELIMITER.toRegex())
-                ?.dropLastWhile { it.isEmpty() }.forEach {
-                    val result = it.split("/")
-                    assertThat(result.size).isEqualTo(3)
-                    assertThat(result[2].toLong()).isEqualTo(currentTime)
-                }
+        componentCaptor.value
+            .split(ResumeMediaBrowser.DELIMITER.toRegex())
+            .dropLastWhile { it.isEmpty() }
+            .forEach {
+                val result = it.split("/")
+                assertThat(result.size).isEqualTo(3)
+                assertThat(result[2].toLong()).isEqualTo(currentTime)
+            }
         verify(sharedPrefsEditor, times(1)).apply()
     }
 
@@ -417,9 +486,7 @@
         setUpMbsWithValidResolveInfo()
 
         // Set up mocks to return with an error
-        whenever(resumeBrowser.testConnection()).thenAnswer {
-            callbackCaptor.value.onError()
-        }
+        whenever(resumeBrowser.testConnection()).thenAnswer { callbackCaptor.value.onError() }
 
         resumeListener.onMediaDataLoaded(key = KEY, oldKey = null, data)
         executor.runAllReady()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt
similarity index 91%
rename from packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt
index dafaa6b..a04cfd4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.resume
 
 import android.content.ComponentName
 import android.content.Context
@@ -37,8 +37,8 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
 
 private const val PACKAGE_NAME = "package"
 private const val CLASS_NAME = "class"
@@ -47,7 +47,9 @@
 private const val ROOT = "media browser root"
 
 private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+
 private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+
 private fun <T> any(): T = Mockito.any<T>()
 
 @SmallTest
@@ -57,10 +59,8 @@
 
     private lateinit var resumeBrowser: TestableResumeMediaBrowser
     private val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
-    private val description = MediaDescription.Builder()
-            .setTitle(TITLE)
-            .setMediaId(MEDIA_ID)
-            .build()
+    private val description =
+        MediaDescription.Builder().setTitle(TITLE).setMediaId(MEDIA_ID).build()
 
     @Mock lateinit var callback: ResumeMediaBrowser.Callback
     @Mock lateinit var listener: MediaResumeListener
@@ -81,19 +81,20 @@
         MockitoAnnotations.initMocks(this)
 
         whenever(browserFactory.create(any(), capture(connectionCallback), any()))
-                .thenReturn(browser)
+            .thenReturn(browser)
 
         whenever(mediaController.transportControls).thenReturn(transportControls)
         whenever(mediaController.sessionToken).thenReturn(token)
 
-        resumeBrowser = TestableResumeMediaBrowser(
-            context,
-            callback,
-            component,
-            browserFactory,
-            logger,
-            mediaController
-        )
+        resumeBrowser =
+            TestableResumeMediaBrowser(
+                context,
+                callback,
+                component,
+                browserFactory,
+                logger,
+                mediaController
+            )
     }
 
     @Test
@@ -329,30 +330,20 @@
         verify(oldBrowser).disconnect()
     }
 
-    /**
-     * Helper function to mock a failed connection
-     */
+    /** Helper function to mock a failed connection */
     private fun setupBrowserFailed() {
-        whenever(browser.connect()).thenAnswer {
-            connectionCallback.value.onConnectionFailed()
-        }
+        whenever(browser.connect()).thenAnswer { connectionCallback.value.onConnectionFailed() }
     }
 
-    /**
-     * Helper function to mock a successful connection only
-     */
+    /** Helper function to mock a successful connection only */
     private fun setupBrowserConnection() {
-        whenever(browser.connect()).thenAnswer {
-            connectionCallback.value.onConnected()
-        }
+        whenever(browser.connect()).thenAnswer { connectionCallback.value.onConnected() }
         whenever(browser.isConnected()).thenReturn(true)
         whenever(browser.getRoot()).thenReturn(ROOT)
         whenever(browser.sessionToken).thenReturn(token)
     }
 
-    /**
-     * Helper function to mock a successful connection, but no media results
-     */
+    /** Helper function to mock a successful connection, but no media results */
     private fun setupBrowserConnectionNoResults() {
         setupBrowserConnection()
         whenever(browser.subscribe(any(), capture(subscriptionCallback))).thenAnswer {
@@ -360,9 +351,7 @@
         }
     }
 
-    /**
-     * Helper function to mock a successful connection, but no playable results
-     */
+    /** Helper function to mock a successful connection, but no playable results */
     private fun setupBrowserConnectionNotPlayable() {
         setupBrowserConnection()
 
@@ -373,9 +362,7 @@
         }
     }
 
-    /**
-     * Helper function to mock a successful connection with playable media
-     */
+    /** Helper function to mock a successful connection with playable media */
     private fun setupBrowserConnectionValidMedia() {
         setupBrowserConnection()
 
@@ -387,9 +374,7 @@
         }
     }
 
-    /**
-     * Override so media controller use is testable
-     */
+    /** Override so media controller use is testable */
     private class TestableResumeMediaBrowser(
         context: Context,
         callback: Callback,
@@ -403,4 +388,4 @@
             return fakeController
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/AnimationBindHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/AnimationBindHandlerTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/media/AnimationBindHandlerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/AnimationBindHandlerTest.kt
index e4cab18..99f56b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/AnimationBindHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/AnimationBindHandlerTest.kt
@@ -14,26 +14,26 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
 
-import org.mockito.Mockito.`when` as whenever
 import android.graphics.drawable.Animatable2
 import android.graphics.drawable.Drawable
 import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import com.android.systemui.SysuiTestCase
-import junit.framework.Assert.assertTrue
 import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.times
 import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
 
 @SmallTest
@@ -56,8 +56,7 @@
         handler = AnimationBindHandler()
     }
 
-    @After
-    fun tearDown() {}
+    @After fun tearDown() {}
 
     @Test
     fun registerNoAnimations_executeCallbackImmediately() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
similarity index 90%
rename from packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
index f56d42e..5bb74e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
 
 import android.animation.ValueAnimator
 import android.graphics.Color
@@ -22,6 +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.monet.ColorScheme
 import junit.framework.Assert.assertEquals
 import org.junit.After
@@ -67,21 +69,18 @@
         animatingColorTransitionFactory = { _, _, _ -> mockAnimatingTransition }
         whenever(extractColor.invoke(colorScheme)).thenReturn(TARGET_COLOR)
 
-        colorSchemeTransition = ColorSchemeTransition(
-            context, mediaViewHolder, animatingColorTransitionFactory
-        )
+        colorSchemeTransition =
+            ColorSchemeTransition(context, mediaViewHolder, animatingColorTransitionFactory)
 
-        colorTransition = object : AnimatingColorTransition(
-            DEFAULT_COLOR, extractColor, applyColor
-        ) {
-            override fun buildAnimator(): ValueAnimator {
-                return valueAnimator
+        colorTransition =
+            object : AnimatingColorTransition(DEFAULT_COLOR, extractColor, applyColor) {
+                override fun buildAnimator(): ValueAnimator {
+                    return valueAnimator
+                }
             }
-        }
     }
 
-    @After
-    fun tearDown() {}
+    @After fun tearDown() {}
 
     @Test
     fun testColorTransition_nullColorScheme_keepsDefault() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
similarity index 84%
rename from packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
index c41fac7..2026006 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
 
 import android.provider.Settings
 import android.test.suitebuilder.annotation.SmallTest
@@ -48,17 +48,12 @@
 @TestableLooper.RunWithLooper
 class KeyguardMediaControllerTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var mediaHost: MediaHost
-    @Mock
-    private lateinit var bypassController: KeyguardBypassController
-    @Mock
-    private lateinit var statusBarStateController: SysuiStatusBarStateController
-    @Mock
-    private lateinit var configurationController: ConfigurationController
+    @Mock private lateinit var mediaHost: MediaHost
+    @Mock private lateinit var bypassController: KeyguardBypassController
+    @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+    @Mock private lateinit var configurationController: ConfigurationController
 
-    @JvmField @Rule
-    val mockito = MockitoJUnit.rule()
+    @JvmField @Rule val mockito = MockitoJUnit.rule()
 
     private val mediaContainerView: MediaContainerView = MediaContainerView(context, null)
     private val hostView = UniqueObjectHostView(context)
@@ -76,15 +71,16 @@
         hostView.layoutParams = FrameLayout.LayoutParams(100, 100)
         testableLooper = TestableLooper.get(this)
         fakeHandler = FakeHandler(testableLooper.looper)
-        keyguardMediaController = KeyguardMediaController(
-            mediaHost,
-            bypassController,
-            statusBarStateController,
-            context,
-            settings,
-            fakeHandler,
-            configurationController,
-        )
+        keyguardMediaController =
+            KeyguardMediaController(
+                mediaHost,
+                bypassController,
+                statusBarStateController,
+                context,
+                settings,
+                fakeHandler,
+                configurationController,
+            )
         keyguardMediaController.attachSinglePaneContainer(mediaContainerView)
         keyguardMediaController.useSplitShade = false
     }
@@ -153,8 +149,10 @@
         keyguardMediaController.attachSplitShadeContainer(splitShadeContainer)
         keyguardMediaController.useSplitShade = true
 
-        assertTrue("HostView wasn't attached to the split pane container",
-            splitShadeContainer.childCount == 1)
+        assertTrue(
+            "HostView wasn't attached to the split pane container",
+            splitShadeContainer.childCount == 1
+        )
     }
 
     @Test
@@ -162,8 +160,10 @@
         val splitShadeContainer = FrameLayout(context)
         keyguardMediaController.attachSplitShadeContainer(splitShadeContainer)
 
-        assertTrue("HostView wasn't attached to the single pane container",
-            mediaContainerView.childCount == 1)
+        assertTrue(
+            "HostView wasn't attached to the single pane container",
+            mediaContainerView.childCount == 1
+        )
     }
 
     @Test
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/MediaCarouselControllerTest.kt
new file mode 100644
index 0000000..c8e8943
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -0,0 +1,645 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.ui
+
+import android.app.PendingIntent
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.dagger.qualifiers.Main
+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.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.MediaCarouselController.Companion.ANIMATION_BASE_DURATION
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DURATION
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.PAGINATION_DELAY
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.TRANSFORM_BEZIER
+import com.android.systemui.media.controls.ui.MediaHierarchyManager.Companion.LOCATION_QS
+import com.android.systemui.media.controls.util.MediaUiEventLogger
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
+import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+import javax.inject.Provider
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+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.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+private val DATA = MediaTestUtils.emptyMediaData
+
+private val SMARTSPACE_KEY = "smartspace"
+
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidTestingRunner::class)
+class MediaCarouselControllerTest : SysuiTestCase() {
+
+    @Mock lateinit var mediaControlPanelFactory: Provider<MediaControlPanel>
+    @Mock lateinit var panel: MediaControlPanel
+    @Mock lateinit var visualStabilityProvider: VisualStabilityProvider
+    @Mock lateinit var mediaHostStatesManager: MediaHostStatesManager
+    @Mock lateinit var mediaHostState: MediaHostState
+    @Mock lateinit var activityStarter: ActivityStarter
+    @Mock @Main private lateinit var executor: DelayableExecutor
+    @Mock lateinit var mediaDataManager: MediaDataManager
+    @Mock lateinit var configurationController: ConfigurationController
+    @Mock lateinit var falsingCollector: FalsingCollector
+    @Mock lateinit var falsingManager: FalsingManager
+    @Mock lateinit var dumpManager: DumpManager
+    @Mock lateinit var logger: MediaUiEventLogger
+    @Mock lateinit var debugLogger: MediaCarouselControllerLogger
+    @Mock lateinit var mediaViewController: MediaViewController
+    @Mock lateinit var smartspaceMediaData: SmartspaceMediaData
+    @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
+    @Captor lateinit var visualStabilityCallback: ArgumentCaptor<OnReorderingAllowedListener>
+
+    private val clock = FakeSystemClock()
+    private lateinit var mediaCarouselController: MediaCarouselController
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        mediaCarouselController =
+            MediaCarouselController(
+                context,
+                mediaControlPanelFactory,
+                visualStabilityProvider,
+                mediaHostStatesManager,
+                activityStarter,
+                clock,
+                executor,
+                mediaDataManager,
+                configurationController,
+                falsingCollector,
+                falsingManager,
+                dumpManager,
+                logger,
+                debugLogger
+            )
+        verify(mediaDataManager).addListener(capture(listener))
+        verify(visualStabilityProvider)
+            .addPersistentReorderingAllowedListener(capture(visualStabilityCallback))
+        whenever(mediaControlPanelFactory.get()).thenReturn(panel)
+        whenever(panel.mediaViewController).thenReturn(mediaViewController)
+        whenever(mediaDataManager.smartspaceMediaData).thenReturn(smartspaceMediaData)
+        MediaPlayerData.clear()
+    }
+
+    @Test
+    fun testPlayerOrdering() {
+        // Test values: key, data, last active time
+        val playingLocal =
+            Triple(
+                "playing local",
+                DATA.copy(
+                    active = true,
+                    isPlaying = true,
+                    playbackLocation = MediaData.PLAYBACK_LOCAL,
+                    resumption = false
+                ),
+                4500L
+            )
+
+        val playingCast =
+            Triple(
+                "playing cast",
+                DATA.copy(
+                    active = true,
+                    isPlaying = true,
+                    playbackLocation = MediaData.PLAYBACK_CAST_LOCAL,
+                    resumption = false
+                ),
+                5000L
+            )
+
+        val pausedLocal =
+            Triple(
+                "paused local",
+                DATA.copy(
+                    active = true,
+                    isPlaying = false,
+                    playbackLocation = MediaData.PLAYBACK_LOCAL,
+                    resumption = false
+                ),
+                1000L
+            )
+
+        val pausedCast =
+            Triple(
+                "paused cast",
+                DATA.copy(
+                    active = true,
+                    isPlaying = false,
+                    playbackLocation = MediaData.PLAYBACK_CAST_LOCAL,
+                    resumption = false
+                ),
+                2000L
+            )
+
+        val playingRcn =
+            Triple(
+                "playing RCN",
+                DATA.copy(
+                    active = true,
+                    isPlaying = true,
+                    playbackLocation = MediaData.PLAYBACK_CAST_REMOTE,
+                    resumption = false
+                ),
+                5000L
+            )
+
+        val pausedRcn =
+            Triple(
+                "paused RCN",
+                DATA.copy(
+                    active = true,
+                    isPlaying = false,
+                    playbackLocation = MediaData.PLAYBACK_CAST_REMOTE,
+                    resumption = false
+                ),
+                5000L
+            )
+
+        val active =
+            Triple(
+                "active",
+                DATA.copy(
+                    active = true,
+                    isPlaying = false,
+                    playbackLocation = MediaData.PLAYBACK_LOCAL,
+                    resumption = true
+                ),
+                250L
+            )
+
+        val resume1 =
+            Triple(
+                "resume 1",
+                DATA.copy(
+                    active = false,
+                    isPlaying = false,
+                    playbackLocation = MediaData.PLAYBACK_LOCAL,
+                    resumption = true
+                ),
+                500L
+            )
+
+        val resume2 =
+            Triple(
+                "resume 2",
+                DATA.copy(
+                    active = false,
+                    isPlaying = false,
+                    playbackLocation = MediaData.PLAYBACK_LOCAL,
+                    resumption = true
+                ),
+                1000L
+            )
+
+        val activeMoreRecent =
+            Triple(
+                "active more recent",
+                DATA.copy(
+                    active = false,
+                    isPlaying = false,
+                    playbackLocation = MediaData.PLAYBACK_LOCAL,
+                    resumption = true,
+                    lastActive = 2L
+                ),
+                1000L
+            )
+
+        val activeLessRecent =
+            Triple(
+                "active less recent",
+                DATA.copy(
+                    active = false,
+                    isPlaying = false,
+                    playbackLocation = MediaData.PLAYBACK_LOCAL,
+                    resumption = true,
+                    lastActive = 1L
+                ),
+                1000L
+            )
+        // Expected ordering for media players:
+        // Actively playing local sessions
+        // Actively playing cast sessions
+        // Paused local and cast sessions, by last active
+        // RCNs
+        // Resume controls, by last active
+
+        val expected =
+            listOf(
+                playingLocal,
+                playingCast,
+                pausedCast,
+                pausedLocal,
+                playingRcn,
+                pausedRcn,
+                active,
+                resume2,
+                resume1
+            )
+
+        expected.forEach {
+            clock.setCurrentTimeMillis(it.third)
+            MediaPlayerData.addMediaPlayer(
+                it.first,
+                it.second.copy(notificationKey = it.first),
+                panel,
+                clock,
+                isSsReactivated = false
+            )
+        }
+
+        for ((index, key) in MediaPlayerData.playerKeys().withIndex()) {
+            assertEquals(expected.get(index).first, key.data.notificationKey)
+        }
+
+        for ((index, key) in MediaPlayerData.visiblePlayerKeys().withIndex()) {
+            assertEquals(expected.get(index).first, key.data.notificationKey)
+        }
+    }
+
+    @Test
+    fun testOrderWithSmartspace_prioritized() {
+        testPlayerOrdering()
+
+        // If smartspace is prioritized
+        MediaPlayerData.addMediaRecommendation(
+            SMARTSPACE_KEY,
+            EMPTY_SMARTSPACE_MEDIA_DATA,
+            panel,
+            true,
+            clock
+        )
+
+        // Then it should be shown immediately after any actively playing controls
+        assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
+    }
+
+    @Test
+    fun testOrderWithSmartspace_prioritized_updatingVisibleMediaPlayers() {
+        testPlayerOrdering()
+
+        // If smartspace is prioritized
+        listener.value.onSmartspaceMediaDataLoaded(
+            SMARTSPACE_KEY,
+            EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
+            true
+        )
+
+        // Then it should be shown immediately after any actively playing controls
+        assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
+        assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(2).isSsMediaRec)
+    }
+
+    @Test
+    fun testOrderWithSmartspace_notPrioritized() {
+        testPlayerOrdering()
+
+        // If smartspace is not prioritized
+        MediaPlayerData.addMediaRecommendation(
+            SMARTSPACE_KEY,
+            EMPTY_SMARTSPACE_MEDIA_DATA,
+            panel,
+            false,
+            clock
+        )
+
+        // Then it should be shown at the end of the carousel's active entries
+        val idx = MediaPlayerData.playerKeys().count { it.data.active } - 1
+        assertTrue(MediaPlayerData.playerKeys().elementAt(idx).isSsMediaRec)
+    }
+
+    @Test
+    fun testPlayingExistingMediaPlayerFromCarousel_visibleMediaPlayersNotUpdated() {
+        testPlayerOrdering()
+        // playing paused player
+        listener.value.onMediaDataLoaded(
+            "paused local",
+            "paused local",
+            DATA.copy(
+                active = true,
+                isPlaying = true,
+                playbackLocation = MediaData.PLAYBACK_LOCAL,
+                resumption = false
+            )
+        )
+        listener.value.onMediaDataLoaded(
+            "playing local",
+            "playing local",
+            DATA.copy(
+                active = true,
+                isPlaying = false,
+                playbackLocation = MediaData.PLAYBACK_LOCAL,
+                resumption = true
+            )
+        )
+
+        assertEquals(
+            MediaPlayerData.getMediaPlayerIndex("paused local"),
+            mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+        )
+        // paused player order should stays the same in visibleMediaPLayer map.
+        // paused player order should be first in mediaPlayer map.
+        assertEquals(
+            MediaPlayerData.visiblePlayerKeys().elementAt(3),
+            MediaPlayerData.playerKeys().elementAt(0)
+        )
+    }
+    @Test
+    fun testSwipeDismiss_logged() {
+        mediaCarouselController.mediaCarouselScrollHandler.dismissCallback.invoke()
+
+        verify(logger).logSwipeDismiss()
+    }
+
+    @Test
+    fun testSettingsButton_logged() {
+        mediaCarouselController.settingsButton.callOnClick()
+
+        verify(logger).logCarouselSettings()
+    }
+
+    @Test
+    fun testLocationChangeQs_logged() {
+        mediaCarouselController.onDesiredLocationChanged(
+            MediaHierarchyManager.LOCATION_QS,
+            mediaHostState,
+            animate = false
+        )
+        verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QS)
+    }
+
+    @Test
+    fun testLocationChangeQqs_logged() {
+        mediaCarouselController.onDesiredLocationChanged(
+            MediaHierarchyManager.LOCATION_QQS,
+            mediaHostState,
+            animate = false
+        )
+        verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS)
+    }
+
+    @Test
+    fun testLocationChangeLockscreen_logged() {
+        mediaCarouselController.onDesiredLocationChanged(
+            MediaHierarchyManager.LOCATION_LOCKSCREEN,
+            mediaHostState,
+            animate = false
+        )
+        verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN)
+    }
+
+    @Test
+    fun testLocationChangeDream_logged() {
+        mediaCarouselController.onDesiredLocationChanged(
+            MediaHierarchyManager.LOCATION_DREAM_OVERLAY,
+            mediaHostState,
+            animate = false
+        )
+        verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY)
+    }
+
+    @Test
+    fun testRecommendationRemoved_logged() {
+        val packageName = "smartspace package"
+        val instanceId = InstanceId.fakeInstanceId(123)
+
+        val smartspaceData =
+            EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = packageName, instanceId = instanceId)
+        MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, smartspaceData, panel, true, clock)
+        mediaCarouselController.removePlayer(SMARTSPACE_KEY)
+
+        verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!))
+    }
+
+    @Test
+    fun testMediaLoaded_ScrollToActivePlayer() {
+        listener.value.onMediaDataLoaded(
+            "playing local",
+            null,
+            DATA.copy(
+                active = true,
+                isPlaying = true,
+                playbackLocation = MediaData.PLAYBACK_LOCAL,
+                resumption = false
+            )
+        )
+        listener.value.onMediaDataLoaded(
+            "paused local",
+            null,
+            DATA.copy(
+                active = true,
+                isPlaying = false,
+                playbackLocation = MediaData.PLAYBACK_LOCAL,
+                resumption = false
+            )
+        )
+        // adding a media recommendation card.
+        listener.value.onSmartspaceMediaDataLoaded(
+            SMARTSPACE_KEY,
+            EMPTY_SMARTSPACE_MEDIA_DATA,
+            false
+        )
+        mediaCarouselController.shouldScrollToKey = true
+        // switching between media players.
+        listener.value.onMediaDataLoaded(
+            "playing local",
+            "playing local",
+            DATA.copy(
+                active = true,
+                isPlaying = false,
+                playbackLocation = MediaData.PLAYBACK_LOCAL,
+                resumption = true
+            )
+        )
+        listener.value.onMediaDataLoaded(
+            "paused local",
+            "paused local",
+            DATA.copy(
+                active = true,
+                isPlaying = true,
+                playbackLocation = MediaData.PLAYBACK_LOCAL,
+                resumption = false
+            )
+        )
+
+        assertEquals(
+            MediaPlayerData.getMediaPlayerIndex("paused local"),
+            mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+        )
+    }
+
+    @Test
+    fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() {
+        listener.value.onSmartspaceMediaDataLoaded(
+            SMARTSPACE_KEY,
+            EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = "PACKAGE_NAME", isActive = true),
+            false
+        )
+        listener.value.onMediaDataLoaded(
+            "playing local",
+            null,
+            DATA.copy(
+                active = true,
+                isPlaying = true,
+                playbackLocation = MediaData.PLAYBACK_LOCAL,
+                resumption = false
+            )
+        )
+
+        var playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local")
+        assertEquals(
+            playerIndex,
+            mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+        )
+        assertEquals(playerIndex, 0)
+
+        // Replaying the same media player one more time.
+        // And check that the card stays in its position.
+        mediaCarouselController.shouldScrollToKey = true
+        listener.value.onMediaDataLoaded(
+            "playing local",
+            null,
+            DATA.copy(
+                active = true,
+                isPlaying = true,
+                playbackLocation = MediaData.PLAYBACK_LOCAL,
+                resumption = false,
+                packageName = "PACKAGE_NAME"
+            )
+        )
+        playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local")
+        assertEquals(playerIndex, 0)
+    }
+
+    @Test
+    fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() {
+        var result = false
+        mediaCarouselController.updateHostVisibility = { result = true }
+
+        whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(true)
+        listener.value.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY, false)
+
+        assertEquals(true, result)
+    }
+
+    @Test
+    fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() {
+        var result = false
+        mediaCarouselController.updateHostVisibility = { result = true }
+
+        whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(false)
+        listener.value.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY, false)
+        assertEquals(false, result)
+
+        visualStabilityCallback.value.onReorderingAllowed()
+        assertEquals(true, result)
+    }
+
+    @Test
+    fun testGetCurrentVisibleMediaContentIntent() {
+        val clickIntent1 = mock(PendingIntent::class.java)
+        val player1 = Triple("player1", DATA.copy(clickIntent = clickIntent1), 1000L)
+        clock.setCurrentTimeMillis(player1.third)
+        MediaPlayerData.addMediaPlayer(
+            player1.first,
+            player1.second.copy(notificationKey = player1.first),
+            panel,
+            clock,
+            isSsReactivated = false
+        )
+
+        assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent1)
+
+        val clickIntent2 = mock(PendingIntent::class.java)
+        val player2 = Triple("player2", DATA.copy(clickIntent = clickIntent2), 2000L)
+        clock.setCurrentTimeMillis(player2.third)
+        MediaPlayerData.addMediaPlayer(
+            player2.first,
+            player2.second.copy(notificationKey = player2.first),
+            panel,
+            clock,
+            isSsReactivated = false
+        )
+
+        // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is
+        // added to the front because it was active more recently.
+        assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
+
+        val clickIntent3 = mock(PendingIntent::class.java)
+        val player3 = Triple("player3", DATA.copy(clickIntent = clickIntent3), 500L)
+        clock.setCurrentTimeMillis(player3.third)
+        MediaPlayerData.addMediaPlayer(
+            player3.first,
+            player3.second.copy(notificationKey = player3.first),
+            panel,
+            clock,
+            isSsReactivated = false
+        )
+
+        // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is
+        // added to the end because it was active less recently.
+        assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
+    }
+
+    @Test
+    fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() {
+        val delta = 0.0001F
+        val paginationSquishMiddle =
+            TRANSFORM_BEZIER.getInterpolation(
+                (PAGINATION_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
+            )
+        val paginationSquishEnd =
+            TRANSFORM_BEZIER.getInterpolation(
+                (PAGINATION_DELAY + DURATION) / ANIMATION_BASE_DURATION
+            )
+        whenever(mediaHostStatesManager.mediaHostStates)
+            .thenReturn(mutableMapOf(LOCATION_QS to mediaHostState))
+        whenever(mediaHostState.visible).thenReturn(true)
+        mediaCarouselController.currentEndLocation = LOCATION_QS
+        whenever(mediaHostState.squishFraction).thenReturn(paginationSquishMiddle)
+        mediaCarouselController.updatePageIndicatorAlpha()
+        assertEquals(mediaCarouselController.pageIndicator.alpha, 0.5F, delta)
+
+        whenever(mediaHostState.squishFraction).thenReturn(paginationSquishEnd)
+        mediaCarouselController.updatePageIndicatorAlpha()
+        assertEquals(mediaCarouselController.pageIndicator.alpha, 1.0F, delta)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
similarity index 80%
rename from packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index 7de5719..5843053 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
 
 import android.animation.Animator
 import android.animation.AnimatorSet
@@ -59,7 +59,20 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bluetooth.BroadcastDialogController
 import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.media.MediaControlPanel.KEY_SMARTSPACE_APP_NAME
+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.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.util.MediaUiEventLogger
 import com.android.systemui.media.dialog.MediaOutputDialogFactory
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
@@ -164,8 +177,8 @@
 
     private lateinit var session: MediaSession
     private lateinit var device: MediaDeviceData
-    private val disabledDevice = MediaDeviceData(false, null, DISABLED_DEVICE_NAME, null,
-            showBroadcastButton = false)
+    private val disabledDevice =
+        MediaDeviceData(false, null, DISABLED_DEVICE_NAME, null, showBroadcastButton = false)
     private lateinit var mediaData: MediaData
     private val clock = FakeSystemClock()
     @Mock private lateinit var logger: MediaUiEventLogger
@@ -212,24 +225,27 @@
         whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE)
         context.setMockPackageManager(packageManager)
 
-        player = object : MediaControlPanel(
-            context,
-            bgExecutor,
-            mainExecutor,
-            activityStarter,
-            broadcastSender,
-            mediaViewController,
-            seekBarViewModel,
-            Lazy { mediaDataManager },
-            mediaOutputDialogFactory,
-            mediaCarouselController,
-            falsingManager,
-            clock,
-            logger,
-            keyguardStateController,
-            activityIntentHelper,
-            lockscreenUserManager,
-            broadcastDialogController) {
+        player =
+            object :
+                MediaControlPanel(
+                    context,
+                    bgExecutor,
+                    mainExecutor,
+                    activityStarter,
+                    broadcastSender,
+                    mediaViewController,
+                    seekBarViewModel,
+                    Lazy { mediaDataManager },
+                    mediaOutputDialogFactory,
+                    mediaCarouselController,
+                    falsingManager,
+                    clock,
+                    logger,
+                    keyguardStateController,
+                    activityIntentHelper,
+                    lockscreenUserManager,
+                    broadcastDialogController
+                ) {
                 override fun loadAnimator(
                     animId: Int,
                     otionInterpolator: Interpolator,
@@ -250,18 +266,20 @@
         // Set valid recommendation data
         val extras = Bundle()
         extras.putString(KEY_SMARTSPACE_APP_NAME, REC_APP_NAME)
-        val intent = Intent().apply {
-            putExtras(extras)
-            setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-        }
+        val intent =
+            Intent().apply {
+                putExtras(extras)
+                setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+            }
         whenever(smartspaceAction.intent).thenReturn(intent)
         whenever(smartspaceAction.extras).thenReturn(extras)
-        smartspaceData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
-            packageName = PACKAGE,
-            instanceId = instanceId,
-            recommendations = listOf(smartspaceAction, smartspaceAction, smartspaceAction),
-            cardAction = smartspaceAction
-        )
+        smartspaceData =
+            EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+                packageName = PACKAGE,
+                instanceId = instanceId,
+                recommendations = listOf(smartspaceAction, smartspaceAction, smartspaceAction),
+                cardAction = smartspaceAction
+            )
     }
 
     private fun initGutsViewHolderMocks() {
@@ -279,36 +297,39 @@
     }
 
     private fun initDeviceMediaData(shouldShowBroadcastButton: Boolean, name: String) {
-        device = MediaDeviceData(true, null, name, null,
-                showBroadcastButton = shouldShowBroadcastButton)
+        device =
+            MediaDeviceData(true, null, name, null, showBroadcastButton = shouldShowBroadcastButton)
 
         // Create media session
-        val metadataBuilder = MediaMetadata.Builder().apply {
-            putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
-            putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
-        }
-        val playbackBuilder = PlaybackState.Builder().apply {
-            setState(PlaybackState.STATE_PAUSED, 6000L, 1f)
-            setActions(PlaybackState.ACTION_PLAY)
-        }
-        session = MediaSession(context, SESSION_KEY).apply {
-            setMetadata(metadataBuilder.build())
-            setPlaybackState(playbackBuilder.build())
-        }
+        val metadataBuilder =
+            MediaMetadata.Builder().apply {
+                putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
+                putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
+            }
+        val playbackBuilder =
+            PlaybackState.Builder().apply {
+                setState(PlaybackState.STATE_PAUSED, 6000L, 1f)
+                setActions(PlaybackState.ACTION_PLAY)
+            }
+        session =
+            MediaSession(context, SESSION_KEY).apply {
+                setMetadata(metadataBuilder.build())
+                setPlaybackState(playbackBuilder.build())
+            }
         session.setActive(true)
 
-        mediaData = MediaTestUtils.emptyMediaData.copy(
+        mediaData =
+            MediaTestUtils.emptyMediaData.copy(
                 artist = ARTIST,
                 song = TITLE,
                 packageName = PACKAGE,
                 token = session.sessionToken,
                 device = device,
-                instanceId = instanceId)
+                instanceId = instanceId
+            )
     }
 
-    /**
-     * Initialize elements in media view holder
-     */
+    /** Initialize elements in media view holder */
     private fun initMediaViewHolderMocks() {
         whenever(seekBarViewModel.progress).thenReturn(seekBarData)
 
@@ -349,7 +370,8 @@
                         action1.id,
                         action2.id,
                         action3.id,
-                        action4.id)
+                        action4.id
+                    )
             }
 
         whenever(viewHolder.player).thenReturn(view)
@@ -394,9 +416,7 @@
         whenever(viewHolder.actionsTopBarrier).thenReturn(actionsTopBarrier)
     }
 
-    /**
-     * Initialize elements for the recommendation view holder
-     */
+    /** Initialize elements for the recommendation view holder */
     private fun initRecommendationViewHolderMocks() {
         recTitle1 = TextView(context)
         recTitle2 = TextView(context)
@@ -419,9 +439,8 @@
             .thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3))
         whenever(recommendationViewHolder.mediaTitles)
             .thenReturn(listOf(recTitle1, recTitle2, recTitle3))
-        whenever(recommendationViewHolder.mediaSubtitles).thenReturn(
-            listOf(recSubtitle1, recSubtitle2, recSubtitle3)
-        )
+        whenever(recommendationViewHolder.mediaSubtitles)
+            .thenReturn(listOf(recSubtitle1, recSubtitle2, recSubtitle3))
 
         whenever(recommendationViewHolder.gutsViewHolder).thenReturn(gutsViewHolder)
 
@@ -453,12 +472,13 @@
     fun bindSemanticActions() {
         val icon = context.getDrawable(android.R.drawable.ic_media_play)
         val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
-        val semanticActions = MediaButton(
-            playOrPause = MediaAction(icon, Runnable {}, "play", bg),
-            nextOrCustom = MediaAction(icon, Runnable {}, "next", bg),
-            custom0 = MediaAction(icon, null, "custom 0", bg),
-            custom1 = MediaAction(icon, null, "custom 1", bg)
-        )
+        val semanticActions =
+            MediaButton(
+                playOrPause = MediaAction(icon, Runnable {}, "play", bg),
+                nextOrCustom = MediaAction(icon, Runnable {}, "next", bg),
+                custom0 = MediaAction(icon, null, "custom 0", bg),
+                custom1 = MediaAction(icon, null, "custom 1", bg)
+            )
         val state = mediaData.copy(semanticActions = semanticActions)
         player.attachPlayer(viewHolder)
         player.bindPlayer(state, PACKAGE)
@@ -501,15 +521,16 @@
         val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
 
         // Setup button state: no prev or next button and their slots reserved
-        val semanticActions = MediaButton(
-            playOrPause = MediaAction(icon, Runnable {}, "play", bg),
-            nextOrCustom = null,
-            prevOrCustom = null,
-            custom0 = MediaAction(icon, null, "custom 0", bg),
-            custom1 = MediaAction(icon, null, "custom 1", bg),
-            false,
-            true
-        )
+        val semanticActions =
+            MediaButton(
+                playOrPause = MediaAction(icon, Runnable {}, "play", bg),
+                nextOrCustom = null,
+                prevOrCustom = null,
+                custom0 = MediaAction(icon, null, "custom 0", bg),
+                custom1 = MediaAction(icon, null, "custom 1", bg),
+                false,
+                true
+            )
         val state = mediaData.copy(semanticActions = semanticActions)
 
         player.attachPlayer(viewHolder)
@@ -530,15 +551,16 @@
         val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
 
         // Setup button state: no prev or next button and their slots reserved
-        val semanticActions = MediaButton(
-            playOrPause = MediaAction(icon, Runnable {}, "play", bg),
-            nextOrCustom = null,
-            prevOrCustom = null,
-            custom0 = MediaAction(icon, null, "custom 0", bg),
-            custom1 = MediaAction(icon, null, "custom 1", bg),
-            true,
-            false
-        )
+        val semanticActions =
+            MediaButton(
+                playOrPause = MediaAction(icon, Runnable {}, "play", bg),
+                nextOrCustom = null,
+                prevOrCustom = null,
+                custom0 = MediaAction(icon, null, "custom 0", bg),
+                custom1 = MediaAction(icon, null, "custom 1", bg),
+                true,
+                false
+            )
         val state = mediaData.copy(semanticActions = semanticActions)
 
         player.attachPlayer(viewHolder)
@@ -646,10 +668,11 @@
         useRealConstraintSets()
 
         val icon = context.getDrawable(android.R.drawable.ic_media_play)
-        val semanticActions = MediaButton(
-            playOrPause = MediaAction(icon, Runnable {}, "play", null),
-            nextOrCustom = MediaAction(icon, Runnable {}, "next", null)
-        )
+        val semanticActions =
+            MediaButton(
+                playOrPause = MediaAction(icon, Runnable {}, "play", null),
+                nextOrCustom = MediaAction(icon, Runnable {}, "next", null)
+            )
         val state = mediaData.copy(semanticActions = semanticActions)
 
         player.attachPlayer(viewHolder)
@@ -719,9 +742,8 @@
         useRealConstraintSets()
 
         val icon = context.getDrawable(android.R.drawable.ic_media_play)
-        val semanticActions = MediaButton(
-            nextOrCustom = MediaAction(icon, Runnable {}, "next", null)
-        )
+        val semanticActions =
+            MediaButton(nextOrCustom = MediaAction(icon, Runnable {}, "next", null))
         val state = mediaData.copy(semanticActions = semanticActions)
 
         player.attachPlayer(viewHolder)
@@ -736,10 +758,11 @@
     @Test
     fun bind_notScrubbing_scrubbingViewsGone() {
         val icon = context.getDrawable(android.R.drawable.ic_media_play)
-        val semanticActions = MediaButton(
-            prevOrCustom = MediaAction(icon, {}, "prev", null),
-            nextOrCustom = MediaAction(icon, {}, "next", null)
-        )
+        val semanticActions =
+            MediaButton(
+                prevOrCustom = MediaAction(icon, {}, "prev", null),
+                nextOrCustom = MediaAction(icon, {}, "next", null)
+            )
         val state = mediaData.copy(semanticActions = semanticActions)
 
         player.attachPlayer(viewHolder)
@@ -770,10 +793,8 @@
     @Test
     fun setIsScrubbing_noPrevButton_scrubbingTimesNotShown() {
         val icon = context.getDrawable(android.R.drawable.ic_media_play)
-        val semanticActions = MediaButton(
-            prevOrCustom = null,
-            nextOrCustom = MediaAction(icon, {}, "next", null)
-        )
+        val semanticActions =
+            MediaButton(prevOrCustom = null, nextOrCustom = MediaAction(icon, {}, "next", null))
         val state = mediaData.copy(semanticActions = semanticActions)
         player.attachPlayer(viewHolder)
         player.bindPlayer(state, PACKAGE)
@@ -790,10 +811,8 @@
     @Test
     fun setIsScrubbing_noNextButton_scrubbingTimesNotShown() {
         val icon = context.getDrawable(android.R.drawable.ic_media_play)
-        val semanticActions = MediaButton(
-            prevOrCustom = MediaAction(icon, {}, "prev", null),
-            nextOrCustom = null
-        )
+        val semanticActions =
+            MediaButton(prevOrCustom = MediaAction(icon, {}, "prev", null), nextOrCustom = null)
         val state = mediaData.copy(semanticActions = semanticActions)
         player.attachPlayer(viewHolder)
         player.bindPlayer(state, PACKAGE)
@@ -810,10 +829,11 @@
     @Test
     fun setIsScrubbing_true_scrubbingViewsShownAndPrevNextHiddenOnlyInExpanded() {
         val icon = context.getDrawable(android.R.drawable.ic_media_play)
-        val semanticActions = MediaButton(
-            prevOrCustom = MediaAction(icon, {}, "prev", null),
-            nextOrCustom = MediaAction(icon, {}, "next", null)
-        )
+        val semanticActions =
+            MediaButton(
+                prevOrCustom = MediaAction(icon, {}, "prev", null),
+                nextOrCustom = MediaAction(icon, {}, "next", null)
+            )
         val state = mediaData.copy(semanticActions = semanticActions)
         player.attachPlayer(viewHolder)
         player.bindPlayer(state, PACKAGE)
@@ -832,10 +852,11 @@
     @Test
     fun setIsScrubbing_trueThenFalse_scrubbingTimeGoneAtEnd() {
         val icon = context.getDrawable(android.R.drawable.ic_media_play)
-        val semanticActions = MediaButton(
-            prevOrCustom = MediaAction(icon, {}, "prev", null),
-            nextOrCustom = MediaAction(icon, {}, "next", null)
-        )
+        val semanticActions =
+            MediaButton(
+                prevOrCustom = MediaAction(icon, {}, "prev", null),
+                nextOrCustom = MediaAction(icon, {}, "next", null)
+            )
         val state = mediaData.copy(semanticActions = semanticActions)
 
         player.attachPlayer(viewHolder)
@@ -859,18 +880,20 @@
     fun bindNotificationActions() {
         val icon = context.getDrawable(android.R.drawable.ic_media_play)
         val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
-        val actions = listOf(
-            MediaAction(icon, Runnable {}, "previous", bg),
-            MediaAction(icon, Runnable {}, "play", bg),
-            MediaAction(icon, null, "next", bg),
-            MediaAction(icon, null, "custom 0", bg),
-            MediaAction(icon, Runnable {}, "custom 1", bg)
-        )
-        val state = mediaData.copy(
-            actions = actions,
-            actionsToShowInCompact = listOf(1, 2),
-            semanticActions = null
-        )
+        val actions =
+            listOf(
+                MediaAction(icon, Runnable {}, "previous", bg),
+                MediaAction(icon, Runnable {}, "play", bg),
+                MediaAction(icon, null, "next", bg),
+                MediaAction(icon, null, "custom 0", bg),
+                MediaAction(icon, Runnable {}, "custom 1", bg)
+            )
+        val state =
+            mediaData.copy(
+                actions = actions,
+                actionsToShowInCompact = listOf(1, 2),
+                semanticActions = null
+            )
 
         player.attachPlayer(viewHolder)
         player.bindPlayer(state, PACKAGE)
@@ -918,15 +941,12 @@
 
         val icon = context.getDrawable(R.drawable.ic_media_play)
         val bg = context.getDrawable(R.drawable.ic_media_play_container)
-        val semanticActions0 = MediaButton(
-            playOrPause = MediaAction(mockAvd0, Runnable {}, "play", null)
-        )
-        val semanticActions1 = MediaButton(
-            playOrPause = MediaAction(mockAvd1, Runnable {}, "pause", null)
-        )
-        val semanticActions2 = MediaButton(
-            playOrPause = MediaAction(mockAvd2, Runnable {}, "loading", null)
-        )
+        val semanticActions0 =
+            MediaButton(playOrPause = MediaAction(mockAvd0, Runnable {}, "play", null))
+        val semanticActions1 =
+            MediaButton(playOrPause = MediaAction(mockAvd1, Runnable {}, "pause", null))
+        val semanticActions2 =
+            MediaButton(playOrPause = MediaAction(mockAvd2, Runnable {}, "loading", null))
         val state0 = mediaData.copy(semanticActions = semanticActions0)
         val state1 = mediaData.copy(semanticActions = semanticActions1)
         val state2 = mediaData.copy(semanticActions = semanticActions2)
@@ -1089,11 +1109,10 @@
 
         val mockAvd0 = mock(AnimatedVectorDrawable::class.java)
         whenever(mockAvd0.mutate()).thenReturn(mockAvd0)
-        val semanticActions0 = MediaButton(
-                playOrPause = MediaAction(mockAvd0, Runnable {}, "play", null)
-        )
-        val state = mediaData.copy(resumption = true, semanticActions = semanticActions0,
-                isPlaying = false)
+        val semanticActions0 =
+            MediaButton(playOrPause = MediaAction(mockAvd0, Runnable {}, "play", null))
+        val state =
+            mediaData.copy(resumption = true, semanticActions = semanticActions0, isPlaying = false)
         player.attachPlayer(viewHolder)
         player.bindPlayer(state, PACKAGE)
         assertThat(seamlessText.getText()).isEqualTo(APP_NAME)
@@ -1432,9 +1451,8 @@
 
     @Test
     fun actionPlayPauseClick_isLogged() {
-        val semanticActions = MediaButton(
-            playOrPause = MediaAction(null, Runnable {}, "play", null)
-        )
+        val semanticActions =
+            MediaButton(playOrPause = MediaAction(null, Runnable {}, "play", null))
         val data = mediaData.copy(semanticActions = semanticActions)
 
         player.attachPlayer(viewHolder)
@@ -1446,9 +1464,8 @@
 
     @Test
     fun actionPrevClick_isLogged() {
-        val semanticActions = MediaButton(
-            prevOrCustom = MediaAction(null, Runnable {}, "previous", null)
-        )
+        val semanticActions =
+            MediaButton(prevOrCustom = MediaAction(null, Runnable {}, "previous", null))
         val data = mediaData.copy(semanticActions = semanticActions)
 
         player.attachPlayer(viewHolder)
@@ -1460,9 +1477,8 @@
 
     @Test
     fun actionNextClick_isLogged() {
-        val semanticActions = MediaButton(
-            nextOrCustom = MediaAction(null, Runnable {}, "next", null)
-        )
+        val semanticActions =
+            MediaButton(nextOrCustom = MediaAction(null, Runnable {}, "next", null))
         val data = mediaData.copy(semanticActions = semanticActions)
 
         player.attachPlayer(viewHolder)
@@ -1474,9 +1490,8 @@
 
     @Test
     fun actionCustom0Click_isLogged() {
-        val semanticActions = MediaButton(
-            custom0 = MediaAction(null, Runnable {}, "custom 0", null)
-        )
+        val semanticActions =
+            MediaButton(custom0 = MediaAction(null, Runnable {}, "custom 0", null))
         val data = mediaData.copy(semanticActions = semanticActions)
 
         player.attachPlayer(viewHolder)
@@ -1488,9 +1503,8 @@
 
     @Test
     fun actionCustom1Click_isLogged() {
-        val semanticActions = MediaButton(
-            custom1 = MediaAction(null, Runnable {}, "custom 1", null)
-        )
+        val semanticActions =
+            MediaButton(custom1 = MediaAction(null, Runnable {}, "custom 1", null))
         val data = mediaData.copy(semanticActions = semanticActions)
 
         player.attachPlayer(viewHolder)
@@ -1502,13 +1516,14 @@
 
     @Test
     fun actionCustom2Click_isLogged() {
-        val actions = listOf(
-            MediaAction(null, Runnable {}, "action 0", null),
-            MediaAction(null, Runnable {}, "action 1", null),
-            MediaAction(null, Runnable {}, "action 2", null),
-            MediaAction(null, Runnable {}, "action 3", null),
-            MediaAction(null, Runnable {}, "action 4", null)
-        )
+        val actions =
+            listOf(
+                MediaAction(null, Runnable {}, "action 0", null),
+                MediaAction(null, Runnable {}, "action 1", null),
+                MediaAction(null, Runnable {}, "action 2", null),
+                MediaAction(null, Runnable {}, "action 3", null),
+                MediaAction(null, Runnable {}, "action 4", null)
+            )
         val data = mediaData.copy(actions = actions)
 
         player.attachPlayer(viewHolder)
@@ -1520,13 +1535,14 @@
 
     @Test
     fun actionCustom3Click_isLogged() {
-        val actions = listOf(
-            MediaAction(null, Runnable {}, "action 0", null),
-            MediaAction(null, Runnable {}, "action 1", null),
-            MediaAction(null, Runnable {}, "action 2", null),
-            MediaAction(null, Runnable {}, "action 3", null),
-            MediaAction(null, Runnable {}, "action 4", null)
-        )
+        val actions =
+            listOf(
+                MediaAction(null, Runnable {}, "action 0", null),
+                MediaAction(null, Runnable {}, "action 1", null),
+                MediaAction(null, Runnable {}, "action 2", null),
+                MediaAction(null, Runnable {}, "action 3", null),
+                MediaAction(null, Runnable {}, "action 4", null)
+            )
         val data = mediaData.copy(actions = actions)
 
         player.attachPlayer(viewHolder)
@@ -1538,13 +1554,14 @@
 
     @Test
     fun actionCustom4Click_isLogged() {
-        val actions = listOf(
-            MediaAction(null, Runnable {}, "action 0", null),
-            MediaAction(null, Runnable {}, "action 1", null),
-            MediaAction(null, Runnable {}, "action 2", null),
-            MediaAction(null, Runnable {}, "action 3", null),
-            MediaAction(null, Runnable {}, "action 4", null)
-        )
+        val actions =
+            listOf(
+                MediaAction(null, Runnable {}, "action 0", null),
+                MediaAction(null, Runnable {}, "action 1", null),
+                MediaAction(null, Runnable {}, "action 2", null),
+                MediaAction(null, Runnable {}, "action 3", null),
+                MediaAction(null, Runnable {}, "action 4", null)
+            )
         val data = mediaData.copy(actions = actions)
 
         player.attachPlayer(viewHolder)
@@ -1608,8 +1625,7 @@
 
         // THEN it shows without dismissing keyguard first
         captor.value.onClick(viewHolder.player)
-        verify(activityStarter).startActivity(eq(clickIntent), eq(true),
-                nullable(), eq(true))
+        verify(activityStarter).startActivity(eq(clickIntent), eq(true), nullable(), eq(true))
     }
 
     @Test
@@ -1697,20 +1713,22 @@
     fun bindRecommendation_listHasTooFewRecs_notDisplayed() {
         player.attachRecommendation(recommendationViewHolder)
         val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
-        val data = smartspaceData.copy(
-            recommendations = listOf(
-                SmartspaceAction.Builder("id1", "title1")
-                    .setSubtitle("subtitle1")
-                    .setIcon(icon)
-                    .setExtras(Bundle.EMPTY)
-                    .build(),
-                SmartspaceAction.Builder("id2", "title2")
-                    .setSubtitle("subtitle2")
-                    .setIcon(icon)
-                    .setExtras(Bundle.EMPTY)
-                    .build(),
+        val data =
+            smartspaceData.copy(
+                recommendations =
+                    listOf(
+                        SmartspaceAction.Builder("id1", "title1")
+                            .setSubtitle("subtitle1")
+                            .setIcon(icon)
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id2", "title2")
+                            .setSubtitle("subtitle2")
+                            .setIcon(icon)
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                    )
             )
-        )
 
         player.bindRecommendation(data)
 
@@ -1722,30 +1740,32 @@
     fun bindRecommendation_listHasTooFewRecsWithIcons_notDisplayed() {
         player.attachRecommendation(recommendationViewHolder)
         val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
-        val data = smartspaceData.copy(
-            recommendations = listOf(
-                SmartspaceAction.Builder("id1", "title1")
-                    .setSubtitle("subtitle1")
-                    .setIcon(icon)
-                    .setExtras(Bundle.EMPTY)
-                    .build(),
-                SmartspaceAction.Builder("id2", "title2")
-                    .setSubtitle("subtitle2")
-                    .setIcon(icon)
-                    .setExtras(Bundle.EMPTY)
-                    .build(),
-                SmartspaceAction.Builder("id2", "empty icon 1")
-                    .setSubtitle("subtitle2")
-                    .setIcon(null)
-                    .setExtras(Bundle.EMPTY)
-                    .build(),
-                SmartspaceAction.Builder("id2", "empty icon 2")
-                    .setSubtitle("subtitle2")
-                    .setIcon(null)
-                    .setExtras(Bundle.EMPTY)
-                    .build(),
+        val data =
+            smartspaceData.copy(
+                recommendations =
+                    listOf(
+                        SmartspaceAction.Builder("id1", "title1")
+                            .setSubtitle("subtitle1")
+                            .setIcon(icon)
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id2", "title2")
+                            .setSubtitle("subtitle2")
+                            .setIcon(icon)
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id2", "empty icon 1")
+                            .setSubtitle("subtitle2")
+                            .setIcon(null)
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id2", "empty icon 2")
+                            .setSubtitle("subtitle2")
+                            .setIcon(null)
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                    )
             )
-        )
 
         player.bindRecommendation(data)
 
@@ -1765,25 +1785,27 @@
         val subtitle3 = "Subtitle3"
         val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
 
-        val data = smartspaceData.copy(
-            recommendations = listOf(
-                SmartspaceAction.Builder("id1", title1)
-                    .setSubtitle(subtitle1)
-                    .setIcon(icon)
-                    .setExtras(Bundle.EMPTY)
-                    .build(),
-                SmartspaceAction.Builder("id2", title2)
-                    .setSubtitle(subtitle2)
-                    .setIcon(icon)
-                    .setExtras(Bundle.EMPTY)
-                    .build(),
-                SmartspaceAction.Builder("id3", title3)
-                    .setSubtitle(subtitle3)
-                    .setIcon(icon)
-                    .setExtras(Bundle.EMPTY)
-                    .build()
+        val data =
+            smartspaceData.copy(
+                recommendations =
+                    listOf(
+                        SmartspaceAction.Builder("id1", title1)
+                            .setSubtitle(subtitle1)
+                            .setIcon(icon)
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id2", title2)
+                            .setSubtitle(subtitle2)
+                            .setIcon(icon)
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id3", title3)
+                            .setSubtitle(subtitle3)
+                            .setIcon(icon)
+                            .setExtras(Bundle.EMPTY)
+                            .build()
+                    )
             )
-        )
         player.bindRecommendation(data)
 
         assertThat(recTitle1.text).isEqualTo(title1)
@@ -1798,15 +1820,17 @@
     fun bindRecommendation_noTitle_subtitleNotShown() {
         player.attachRecommendation(recommendationViewHolder)
 
-        val data = smartspaceData.copy(
-            recommendations = listOf(
-                SmartspaceAction.Builder("id1", "")
-                    .setSubtitle("fake subtitle")
-                    .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
-                    .setExtras(Bundle.EMPTY)
-                    .build()
+        val data =
+            smartspaceData.copy(
+                recommendations =
+                    listOf(
+                        SmartspaceAction.Builder("id1", "")
+                            .setSubtitle("fake subtitle")
+                            .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
+                            .setExtras(Bundle.EMPTY)
+                            .build()
+                    )
             )
-        )
         player.bindRecommendation(data)
 
         assertThat(recSubtitle1.text).isEqualTo("")
@@ -1818,25 +1842,27 @@
         player.attachRecommendation(recommendationViewHolder)
 
         val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
-        val data = smartspaceData.copy(
-            recommendations = listOf(
-                SmartspaceAction.Builder("id1", "")
-                    .setSubtitle("fake subtitle")
-                    .setIcon(icon)
-                    .setExtras(Bundle.EMPTY)
-                    .build(),
-                SmartspaceAction.Builder("id2", "title2")
-                    .setSubtitle("fake subtitle")
-                    .setIcon(icon)
-                    .setExtras(Bundle.EMPTY)
-                    .build(),
-                SmartspaceAction.Builder("id3", "")
-                    .setSubtitle("fake subtitle")
-                    .setIcon(icon)
-                    .setExtras(Bundle.EMPTY)
-                    .build()
+        val data =
+            smartspaceData.copy(
+                recommendations =
+                    listOf(
+                        SmartspaceAction.Builder("id1", "")
+                            .setSubtitle("fake subtitle")
+                            .setIcon(icon)
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id2", "title2")
+                            .setSubtitle("fake subtitle")
+                            .setIcon(icon)
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id3", "")
+                            .setSubtitle("fake subtitle")
+                            .setIcon(icon)
+                            .setExtras(Bundle.EMPTY)
+                            .build()
+                    )
             )
-        )
         player.bindRecommendation(data)
 
         assertThat(expandedSet.getVisibility(recTitle1.id)).isEqualTo(ConstraintSet.VISIBLE)
@@ -1850,25 +1876,27 @@
         player.attachRecommendation(recommendationViewHolder)
 
         val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
-        val data = smartspaceData.copy(
-            recommendations = listOf(
-                SmartspaceAction.Builder("id1", "")
-                    .setSubtitle("")
-                    .setIcon(icon)
-                    .setExtras(Bundle.EMPTY)
-                    .build(),
-                SmartspaceAction.Builder("id2", "title2")
-                    .setSubtitle("")
-                    .setIcon(icon)
-                    .setExtras(Bundle.EMPTY)
-                    .build(),
-                SmartspaceAction.Builder("id3", "title3")
-                    .setSubtitle("subtitle3")
-                    .setIcon(icon)
-                    .setExtras(Bundle.EMPTY)
-                    .build()
+        val data =
+            smartspaceData.copy(
+                recommendations =
+                    listOf(
+                        SmartspaceAction.Builder("id1", "")
+                            .setSubtitle("")
+                            .setIcon(icon)
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id2", "title2")
+                            .setSubtitle("")
+                            .setIcon(icon)
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id3", "title3")
+                            .setSubtitle("subtitle3")
+                            .setIcon(icon)
+                            .setExtras(Bundle.EMPTY)
+                            .build()
+                    )
             )
-        )
         player.bindRecommendation(data)
 
         assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.VISIBLE)
@@ -1880,25 +1908,27 @@
     fun bindRecommendation_noneHaveSubtitles_subtitleViewsGone() {
         useRealConstraintSets()
         player.attachRecommendation(recommendationViewHolder)
-        val data = smartspaceData.copy(
-            recommendations = listOf(
-                SmartspaceAction.Builder("id1", "title1")
-                    .setSubtitle("")
-                    .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
-                    .setExtras(Bundle.EMPTY)
-                    .build(),
-                SmartspaceAction.Builder("id2", "title2")
-                    .setSubtitle("")
-                    .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm))
-                    .setExtras(Bundle.EMPTY)
-                    .build(),
-                SmartspaceAction.Builder("id3", "title3")
-                    .setSubtitle("")
-                    .setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata))
-                    .setExtras(Bundle.EMPTY)
-                    .build()
+        val data =
+            smartspaceData.copy(
+                recommendations =
+                    listOf(
+                        SmartspaceAction.Builder("id1", "title1")
+                            .setSubtitle("")
+                            .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id2", "title2")
+                            .setSubtitle("")
+                            .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm))
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id3", "title3")
+                            .setSubtitle("")
+                            .setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata))
+                            .setExtras(Bundle.EMPTY)
+                            .build()
+                    )
             )
-        )
 
         player.bindRecommendation(data)
 
@@ -1911,25 +1941,27 @@
     fun bindRecommendation_noneHaveTitles_titleAndSubtitleViewsGone() {
         useRealConstraintSets()
         player.attachRecommendation(recommendationViewHolder)
-        val data = smartspaceData.copy(
-            recommendations = listOf(
-                SmartspaceAction.Builder("id1", "")
-                    .setSubtitle("subtitle1")
-                    .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
-                    .setExtras(Bundle.EMPTY)
-                    .build(),
-                SmartspaceAction.Builder("id2", "")
-                    .setSubtitle("subtitle2")
-                    .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm))
-                    .setExtras(Bundle.EMPTY)
-                    .build(),
-                SmartspaceAction.Builder("id3", "")
-                    .setSubtitle("subtitle3")
-                    .setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata))
-                    .setExtras(Bundle.EMPTY)
-                    .build()
+        val data =
+            smartspaceData.copy(
+                recommendations =
+                    listOf(
+                        SmartspaceAction.Builder("id1", "")
+                            .setSubtitle("subtitle1")
+                            .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id2", "")
+                            .setSubtitle("subtitle2")
+                            .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm))
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id3", "")
+                            .setSubtitle("subtitle3")
+                            .setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata))
+                            .setExtras(Bundle.EMPTY)
+                            .build()
+                    )
             )
-        )
 
         player.bindRecommendation(data)
 
@@ -1942,20 +1974,23 @@
     }
 
     private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener =
-        withArgCaptor { verify(seekBarViewModel).setScrubbingChangeListener(capture()) }
+        withArgCaptor {
+            verify(seekBarViewModel).setScrubbingChangeListener(capture())
+        }
 
-    private fun getEnabledChangeListener(): SeekBarViewModel.EnabledChangeListener =
-        withArgCaptor { verify(seekBarViewModel).setEnabledChangeListener(capture()) }
+    private fun getEnabledChangeListener(): SeekBarViewModel.EnabledChangeListener = withArgCaptor {
+        verify(seekBarViewModel).setEnabledChangeListener(capture())
+    }
 
     /**
-     *  Update our test to use real ConstraintSets instead of mocks.
+     * Update our test to use real ConstraintSets instead of mocks.
      *
-     *  Some item visibilities, such as the seekbar visibility, are dependent on other action's
-     *  visibilities. If we use mocks for the ConstraintSets, then action visibility changes are
-     *  just thrown away instead of being saved for reference later. This method sets us up to use
-     *  ConstraintSets so that we do save visibility changes.
+     * Some item visibilities, such as the seekbar visibility, are dependent on other action's
+     * visibilities. If we use mocks for the ConstraintSets, then action visibility changes are just
+     * thrown away instead of being saved for reference later. This method sets us up to use
+     * ConstraintSets so that we do save visibility changes.
      *
-     *  TODO(b/229740380): Can/should we use real expanded and collapsed sets for all tests?
+     * TODO(b/229740380): Can/should we use real expanded and collapsed sets for all tests?
      */
     private fun useRealConstraintSets() {
         expandedSet = ConstraintSet()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
similarity index 85%
rename from packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index 954b438..071604d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
 
 import android.graphics.Rect
 import android.provider.Settings
@@ -84,10 +84,8 @@
     private lateinit var statusBarCallback: ArgumentCaptor<(StatusBarStateController.StateListener)>
     @Captor
     private lateinit var dreamOverlayCallback:
-            ArgumentCaptor<(DreamOverlayStateController.Callback)>
-    @JvmField
-    @Rule
-    val mockito = MockitoJUnit.rule()
+        ArgumentCaptor<(DreamOverlayStateController.Callback)>
+    @JvmField @Rule val mockito = MockitoJUnit.rule()
     private lateinit var mediaHierarchyManager: MediaHierarchyManager
     private lateinit var mediaFrame: ViewGroup
     private val configurationController = FakeConfigurationController()
@@ -98,13 +96,15 @@
 
     @Before
     fun setup() {
-        context.getOrCreateTestableResources().addOverride(
-                R.bool.config_use_split_notification_shade, false)
+        context
+            .getOrCreateTestableResources()
+            .addOverride(R.bool.config_use_split_notification_shade, false)
         mediaFrame = FrameLayout(context)
         testableLooper = TestableLooper.get(this)
         fakeHandler = FakeHandler(testableLooper.looper)
         whenever(mediaCarouselController.mediaFrame).thenReturn(mediaFrame)
-        mediaHierarchyManager = MediaHierarchyManager(
+        mediaHierarchyManager =
+            MediaHierarchyManager(
                 context,
                 statusBarStateController,
                 keyguardStateController,
@@ -116,7 +116,8 @@
                 wakefulnessLifecycle,
                 notifPanelEvents,
                 settings,
-                fakeHandler,)
+                fakeHandler,
+            )
         verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
         verify(statusBarStateController).addCallback(statusBarCallback.capture())
         verify(dreamOverlayStateController).addCallback(dreamOverlayCallback.capture())
@@ -125,7 +126,7 @@
         setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS, QQS_TOP)
         whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
         whenever(mediaCarouselController.mediaCarouselScrollHandler)
-                .thenReturn(mediaCarouselScrollHandler)
+            .thenReturn(mediaCarouselScrollHandler)
         val observer = wakefullnessObserver.value
         assertNotNull("lifecycle observer wasn't registered", observer)
         observer.onFinishedWakingUp()
@@ -151,30 +152,53 @@
     fun testBlockedWhenScreenTurningOff() {
         // Let's set it onto QS:
         mediaHierarchyManager.qsExpansion = 1.0f
-        verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
-                any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
+        verify(mediaCarouselController)
+            .onDesiredLocationChanged(
+                ArgumentMatchers.anyInt(),
+                any(MediaHostState::class.java),
+                anyBoolean(),
+                anyLong(),
+                anyLong()
+            )
         val observer = wakefullnessObserver.value
         assertNotNull("lifecycle observer wasn't registered", observer)
         observer.onStartedGoingToSleep()
         clearInvocations(mediaCarouselController)
         mediaHierarchyManager.qsExpansion = 0.0f
         verify(mediaCarouselController, times(0))
-                .onDesiredLocationChanged(ArgumentMatchers.anyInt(),
-                any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
+            .onDesiredLocationChanged(
+                ArgumentMatchers.anyInt(),
+                any(MediaHostState::class.java),
+                anyBoolean(),
+                anyLong(),
+                anyLong()
+            )
     }
 
     @Test
     fun testAllowedWhenNotTurningOff() {
         // Let's set it onto QS:
         mediaHierarchyManager.qsExpansion = 1.0f
-        verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
-                any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
+        verify(mediaCarouselController)
+            .onDesiredLocationChanged(
+                ArgumentMatchers.anyInt(),
+                any(MediaHostState::class.java),
+                anyBoolean(),
+                anyLong(),
+                anyLong()
+            )
         val observer = wakefullnessObserver.value
         assertNotNull("lifecycle observer wasn't registered", observer)
         clearInvocations(mediaCarouselController)
         mediaHierarchyManager.qsExpansion = 0.0f
-        verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
-                any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
+        verify(mediaCarouselController)
+            .onDesiredLocationChanged(
+                ArgumentMatchers.anyInt(),
+                any(MediaHostState::class.java),
+                anyBoolean(),
+                anyLong(),
+                anyLong()
+            )
     }
 
     @Test
@@ -183,22 +207,26 @@
 
         // Let's transition all the way to full shade
         mediaHierarchyManager.setTransitionToFullShadeAmount(100000f)
-        verify(mediaCarouselController).onDesiredLocationChanged(
-            eq(MediaHierarchyManager.LOCATION_QQS),
-            any(MediaHostState::class.java),
-            eq(false),
-            anyLong(),
-            anyLong())
+        verify(mediaCarouselController)
+            .onDesiredLocationChanged(
+                eq(MediaHierarchyManager.LOCATION_QQS),
+                any(MediaHostState::class.java),
+                eq(false),
+                anyLong(),
+                anyLong()
+            )
         clearInvocations(mediaCarouselController)
 
         // Let's go back to the lock screen
         mediaHierarchyManager.setTransitionToFullShadeAmount(0.0f)
-        verify(mediaCarouselController).onDesiredLocationChanged(
-            eq(MediaHierarchyManager.LOCATION_LOCKSCREEN),
-            any(MediaHostState::class.java),
-            eq(false),
-            anyLong(),
-            anyLong())
+        verify(mediaCarouselController)
+            .onDesiredLocationChanged(
+                eq(MediaHierarchyManager.LOCATION_LOCKSCREEN),
+                any(MediaHostState::class.java),
+                eq(false),
+                anyLong(),
+                anyLong()
+            )
 
         // Let's make sure alpha is set
         mediaHierarchyManager.setTransitionToFullShadeAmount(2.0f)
@@ -302,7 +330,7 @@
 
         val expectedTranslation = LOCKSCREEN_TOP - QS_TOP
         assertThat(mediaHierarchyManager.getGuidedTransformationTranslationY())
-                .isEqualTo(expectedTranslation)
+            .isEqualTo(expectedTranslation)
     }
 
     @Test
@@ -343,27 +371,31 @@
     fun testDream() {
         goToDream()
         setMediaDreamComplicationEnabled(true)
-        verify(mediaCarouselController).onDesiredLocationChanged(
+        verify(mediaCarouselController)
+            .onDesiredLocationChanged(
                 eq(MediaHierarchyManager.LOCATION_DREAM_OVERLAY),
                 nullable(),
                 eq(false),
                 anyLong(),
-                anyLong())
+                anyLong()
+            )
         clearInvocations(mediaCarouselController)
 
         setMediaDreamComplicationEnabled(false)
-        verify(mediaCarouselController).onDesiredLocationChanged(
+        verify(mediaCarouselController)
+            .onDesiredLocationChanged(
                 eq(MediaHierarchyManager.LOCATION_QQS),
                 any(MediaHostState::class.java),
                 eq(false),
                 anyLong(),
-                anyLong())
+                anyLong()
+            )
     }
 
     private fun enableSplitShade() {
-        context.getOrCreateTestableResources().addOverride(
-            R.bool.config_use_split_notification_shade, true
-        )
+        context
+            .getOrCreateTestableResources()
+            .addOverride(R.bool.config_use_split_notification_shade, true)
         configurationController.notifyConfigurationChanged()
     }
 
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
new file mode 100644
index 0000000..32b822d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2020 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.media.controls.ui
+
+import android.testing.AndroidTestingRunner
+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.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+public class MediaPlayerDataTest : SysuiTestCase() {
+
+    @Mock private lateinit var playerIsPlaying: MediaControlPanel
+    private var systemClock: FakeSystemClock = FakeSystemClock()
+
+    @JvmField @Rule val mockito = MockitoJUnit.rule()
+
+    companion object {
+        val LOCAL = MediaData.PLAYBACK_LOCAL
+        val REMOTE = MediaData.PLAYBACK_CAST_LOCAL
+        val RESUMPTION = true
+        val PLAYING = true
+        val UNDETERMINED = null
+    }
+
+    @Before
+    fun setup() {
+        MediaPlayerData.clear()
+    }
+
+    @Test
+    fun addPlayingThenRemote() {
+        val dataIsPlaying = createMediaData("app1", PLAYING, LOCAL, !RESUMPTION)
+
+        val playerIsRemote = mock(MediaControlPanel::class.java)
+        val dataIsRemote = createMediaData("app2", PLAYING, REMOTE, !RESUMPTION)
+
+        MediaPlayerData.addMediaPlayer(
+            "2",
+            dataIsRemote,
+            playerIsRemote,
+            systemClock,
+            isSsReactivated = false
+        )
+        MediaPlayerData.addMediaPlayer(
+            "1",
+            dataIsPlaying,
+            playerIsPlaying,
+            systemClock,
+            isSsReactivated = false
+        )
+
+        val players = MediaPlayerData.players()
+        assertThat(players).hasSize(2)
+        assertThat(players).containsExactly(playerIsPlaying, playerIsRemote).inOrder()
+    }
+
+    @Test
+    fun switchPlayersPlaying() {
+        val playerIsPlaying1 = mock(MediaControlPanel::class.java)
+        var dataIsPlaying1 = createMediaData("app1", PLAYING, LOCAL, !RESUMPTION)
+
+        val playerIsPlaying2 = mock(MediaControlPanel::class.java)
+        var dataIsPlaying2 = createMediaData("app2", !PLAYING, LOCAL, !RESUMPTION)
+
+        MediaPlayerData.addMediaPlayer(
+            "1",
+            dataIsPlaying1,
+            playerIsPlaying1,
+            systemClock,
+            isSsReactivated = false
+        )
+        systemClock.advanceTime(1)
+        MediaPlayerData.addMediaPlayer(
+            "2",
+            dataIsPlaying2,
+            playerIsPlaying2,
+            systemClock,
+            isSsReactivated = false
+        )
+        systemClock.advanceTime(1)
+
+        dataIsPlaying1 = createMediaData("app1", !PLAYING, LOCAL, !RESUMPTION)
+        dataIsPlaying2 = createMediaData("app2", PLAYING, LOCAL, !RESUMPTION)
+
+        MediaPlayerData.addMediaPlayer(
+            "1",
+            dataIsPlaying1,
+            playerIsPlaying1,
+            systemClock,
+            isSsReactivated = false
+        )
+        systemClock.advanceTime(1)
+
+        MediaPlayerData.addMediaPlayer(
+            "2",
+            dataIsPlaying2,
+            playerIsPlaying2,
+            systemClock,
+            isSsReactivated = false
+        )
+        systemClock.advanceTime(1)
+
+        val players = MediaPlayerData.players()
+        assertThat(players).hasSize(2)
+        assertThat(players).containsExactly(playerIsPlaying2, playerIsPlaying1).inOrder()
+    }
+
+    @Test
+    fun fullOrderTest() {
+        val dataIsPlaying = createMediaData("app1", PLAYING, LOCAL, !RESUMPTION)
+
+        val playerIsPlayingAndRemote = mock(MediaControlPanel::class.java)
+        val dataIsPlayingAndRemote = createMediaData("app2", PLAYING, REMOTE, !RESUMPTION)
+
+        val playerIsStoppedAndLocal = mock(MediaControlPanel::class.java)
+        val dataIsStoppedAndLocal = createMediaData("app3", !PLAYING, LOCAL, !RESUMPTION)
+
+        val playerIsStoppedAndRemote = mock(MediaControlPanel::class.java)
+        val dataIsStoppedAndRemote = createMediaData("app4", !PLAYING, REMOTE, !RESUMPTION)
+
+        val playerCanResume = mock(MediaControlPanel::class.java)
+        val dataCanResume = createMediaData("app5", !PLAYING, LOCAL, RESUMPTION)
+
+        val playerUndetermined = mock(MediaControlPanel::class.java)
+        val dataUndetermined = createMediaData("app6", UNDETERMINED, LOCAL, RESUMPTION)
+
+        MediaPlayerData.addMediaPlayer(
+            "3",
+            dataIsStoppedAndLocal,
+            playerIsStoppedAndLocal,
+            systemClock,
+            isSsReactivated = false
+        )
+        MediaPlayerData.addMediaPlayer(
+            "5",
+            dataIsStoppedAndRemote,
+            playerIsStoppedAndRemote,
+            systemClock,
+            isSsReactivated = false
+        )
+        MediaPlayerData.addMediaPlayer(
+            "4",
+            dataCanResume,
+            playerCanResume,
+            systemClock,
+            isSsReactivated = false
+        )
+        MediaPlayerData.addMediaPlayer(
+            "1",
+            dataIsPlaying,
+            playerIsPlaying,
+            systemClock,
+            isSsReactivated = false
+        )
+        MediaPlayerData.addMediaPlayer(
+            "2",
+            dataIsPlayingAndRemote,
+            playerIsPlayingAndRemote,
+            systemClock,
+            isSsReactivated = false
+        )
+        MediaPlayerData.addMediaPlayer(
+            "6",
+            dataUndetermined,
+            playerUndetermined,
+            systemClock,
+            isSsReactivated = false
+        )
+
+        val players = MediaPlayerData.players()
+        assertThat(players).hasSize(6)
+        assertThat(players)
+            .containsExactly(
+                playerIsPlaying,
+                playerIsPlayingAndRemote,
+                playerIsStoppedAndRemote,
+                playerIsStoppedAndLocal,
+                playerUndetermined,
+                playerCanResume
+            )
+            .inOrder()
+    }
+
+    @Test
+    fun testMoveMediaKeysAround() {
+        val keyA = "a"
+        val keyB = "b"
+
+        val data = createMediaData("app1", PLAYING, LOCAL, !RESUMPTION)
+
+        assertThat(MediaPlayerData.players()).hasSize(0)
+
+        MediaPlayerData.addMediaPlayer(
+            keyA,
+            data,
+            playerIsPlaying,
+            systemClock,
+            isSsReactivated = false
+        )
+        systemClock.advanceTime(1)
+
+        assertThat(MediaPlayerData.players()).hasSize(1)
+        MediaPlayerData.addMediaPlayer(
+            keyB,
+            data,
+            playerIsPlaying,
+            systemClock,
+            isSsReactivated = false
+        )
+        systemClock.advanceTime(1)
+
+        assertThat(MediaPlayerData.players()).hasSize(2)
+
+        MediaPlayerData.moveIfExists(keyA, keyB)
+
+        assertThat(MediaPlayerData.players()).hasSize(1)
+
+        assertThat(MediaPlayerData.getMediaPlayer(keyA)).isNull()
+        assertThat(MediaPlayerData.getMediaPlayer(keyB)).isNotNull()
+    }
+
+    private fun createMediaData(
+        app: String,
+        isPlaying: Boolean?,
+        location: Int,
+        resumption: Boolean
+    ) =
+        MediaTestUtils.emptyMediaData.copy(
+            app = app,
+            packageName = "package: $app",
+            playbackLocation = location,
+            resumption = resumption,
+            notificationKey = "key: $app",
+            isPlaying = isPlaying
+        )
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
similarity index 91%
rename from packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index 622a512..6b76155 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
@@ -22,13 +22,13 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.media.MediaCarouselController.Companion.ANIMATION_BASE_DURATION
-import com.android.systemui.media.MediaCarouselController.Companion.CONTROLS_DELAY
-import com.android.systemui.media.MediaCarouselController.Companion.DETAILS_DELAY
-import com.android.systemui.media.MediaCarouselController.Companion.DURATION
-import com.android.systemui.media.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY
-import com.android.systemui.media.MediaCarouselController.Companion.MEDIATITLES_DELAY
-import com.android.systemui.media.MediaCarouselController.Companion.TRANSFORM_BEZIER
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.ANIMATION_BASE_DURATION
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.CONTROLS_DELAY
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DETAILS_DELAY
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DURATION
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIATITLES_DELAY
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.TRANSFORM_BEZIER
 import com.android.systemui.util.animation.MeasurementInput
 import com.android.systemui.util.animation.TransitionLayout
 import com.android.systemui.util.animation.TransitionViewState
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MetadataAnimationHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MetadataAnimationHandlerTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/media/MetadataAnimationHandlerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MetadataAnimationHandlerTest.kt
index 311aa96..323b781 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MetadataAnimationHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MetadataAnimationHandlerTest.kt
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
 
-import org.mockito.Mockito.`when` as whenever
 import android.animation.Animator
 import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
@@ -29,10 +28,11 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.times
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
 
 @SmallTest
@@ -55,8 +55,7 @@
         handler = MetadataAnimationHandler(exitAnimator, enterAnimator)
     }
 
-    @After
-    fun tearDown() {}
+    @After fun tearDown() {}
 
     @Test
     fun firstBind_startsAnimationSet() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SquigglyProgressTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/SquigglyProgressTest.kt
similarity index 81%
rename from packages/SystemUI/tests/src/com/android/systemui/media/SquigglyProgressTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/SquigglyProgressTest.kt
index d087b0f..d6cff81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SquigglyProgressTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/SquigglyProgressTest.kt
@@ -1,4 +1,20 @@
-package com.android.systemui.media
+/*
+ * 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.media.controls.ui
 
 import android.graphics.Canvas
 import android.graphics.Color
@@ -107,7 +123,6 @@
         val (wavePaint, linePaint) = paintCaptor.getAllValues()
 
         assertThat(wavePaint.color).isEqualTo(tint)
-        assertThat(linePaint.color).isEqualTo(
-                ColorUtils.setAlphaComponent(tint, DISABLED_ALPHA))
+        assertThat(linePaint.color).isEqualTo(ColorUtils.setAlphaComponent(tint, DISABLED_ALPHA))
     }
-}
\ No newline at end of file
+}
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 29188da..ce885c0 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.MediaHost;
+import com.android.systemui.media.controls.ui.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 af53016..ed928a3 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.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.complication.DreamMediaEntryComplication;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.MediaData;
-import com.android.systemui.media.MediaDataManager;
+import com.android.systemui.media.controls.models.player.MediaData;
+import com.android.systemui.media.controls.pipeline.MediaDataManager;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
index 1078cda..e009e86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
@@ -19,9 +19,9 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogcatEchoTracker
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
index 7c83cb7..6a4c0f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
@@ -22,6 +22,9 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -62,6 +65,34 @@
     }
 
     @Test
+    fun getIconFromPackageName_nullPackageName_returnsDefault() {
+        val icon = MediaTttUtils.getIconFromPackageName(context, appPackageName = null, logger)
+
+        val expectedDesc =
+            ContentDescription.Resource(R.string.media_output_dialog_unknown_launch_app_name)
+                .loadContentDescription(context)
+        assertThat(icon.contentDescription.loadContentDescription(context)).isEqualTo(expectedDesc)
+    }
+
+    @Test
+    fun getIconFromPackageName_invalidPackageName_returnsDefault() {
+        val icon = MediaTttUtils.getIconFromPackageName(context, "fakePackageName", logger)
+
+        val expectedDesc =
+            ContentDescription.Resource(R.string.media_output_dialog_unknown_launch_app_name)
+                .loadContentDescription(context)
+        assertThat(icon.contentDescription.loadContentDescription(context)).isEqualTo(expectedDesc)
+    }
+
+    @Test
+    fun getIconFromPackageName_validPackageName_returnsAppInfo() {
+        val icon = MediaTttUtils.getIconFromPackageName(context, PACKAGE_NAME, logger)
+
+        assertThat(icon)
+            .isEqualTo(Icon.Loaded(appIconFromPackageName, ContentDescription.Loaded(APP_NAME)))
+    }
+
+    @Test
     fun getIconInfoFromPackageName_nullPackageName_returnsDefault() {
         val iconInfo =
             MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null, logger)
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 9577274..8c3ae3d 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
@@ -34,6 +34,7 @@
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.taptotransfer.MediaTttFlags
 import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -49,6 +50,7 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
@@ -70,6 +72,8 @@
     @Mock
     private lateinit var configurationController: ConfigurationController
     @Mock
+    private lateinit var mediaTttFlags: MediaTttFlags
+    @Mock
     private lateinit var powerManager: PowerManager
     @Mock
     private lateinit var viewUtil: ViewUtil
@@ -85,6 +89,7 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true)
 
         fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
         whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
@@ -107,6 +112,7 @@
             configurationController,
             powerManager,
             Handler.getMain(),
+            mediaTttFlags,
             receiverUiEventLogger,
             viewUtil,
         )
@@ -118,6 +124,30 @@
     }
 
     @Test
+    fun commandQueueCallback_flagOff_noCallbackAdded() {
+        reset(commandQueue)
+        whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(false)
+
+        controllerReceiver = MediaTttChipControllerReceiver(
+            commandQueue,
+            context,
+            logger,
+            windowManager,
+            FakeExecutor(FakeSystemClock()),
+            accessibilityManager,
+            configurationController,
+            powerManager,
+            Handler.getMain(),
+            mediaTttFlags,
+            receiverUiEventLogger,
+            viewUtil,
+        )
+        controllerReceiver.start()
+
+        verify(commandQueue, never()).addCallback(any())
+    }
+
+    @Test
     fun commandQueueCallback_closeToSender_triggersChip() {
         val appName = "FakeAppName"
         commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
deleted file mode 100644
index 3a8a51d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ /dev/null
@@ -1,868 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.taptotransfer.sender
-
-import android.app.StatusBarManager
-import android.content.Context
-import android.content.pm.ApplicationInfo
-import android.content.pm.PackageManager
-import android.graphics.drawable.Drawable
-import android.media.MediaRoute2Info
-import android.os.PowerManager
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.View
-import android.view.ViewGroup
-import android.view.WindowManager
-import android.view.accessibility.AccessibilityManager
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.test.filters.SmallTest
-import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.internal.statusbar.IUndoMediaTransferCallback
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
-import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.time.FakeSystemClock
-import com.android.systemui.util.view.ViewUtil
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-class MediaTttChipControllerSenderTest : SysuiTestCase() {
-    private lateinit var controllerSender: TestMediaTttChipControllerSender
-
-    @Mock
-    private lateinit var packageManager: PackageManager
-    @Mock
-    private lateinit var applicationInfo: ApplicationInfo
-    @Mock
-    private lateinit var logger: MediaTttLogger
-    @Mock
-    private lateinit var accessibilityManager: AccessibilityManager
-    @Mock
-    private lateinit var configurationController: ConfigurationController
-    @Mock
-    private lateinit var powerManager: PowerManager
-    @Mock
-    private lateinit var windowManager: WindowManager
-    @Mock
-    private lateinit var commandQueue: CommandQueue
-    @Mock
-    private lateinit var falsingManager: FalsingManager
-    @Mock
-    private lateinit var falsingCollector: FalsingCollector
-    @Mock
-    private lateinit var viewUtil: ViewUtil
-    private lateinit var commandQueueCallback: CommandQueue.Callbacks
-    private lateinit var fakeAppIconDrawable: Drawable
-    private lateinit var fakeClock: FakeSystemClock
-    private lateinit var fakeExecutor: FakeExecutor
-    private lateinit var uiEventLoggerFake: UiEventLoggerFake
-    private lateinit var senderUiEventLogger: MediaTttSenderUiEventLogger
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
-        whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME)
-        whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
-        whenever(packageManager.getApplicationInfo(
-            eq(PACKAGE_NAME), any<PackageManager.ApplicationInfoFlags>()
-        )).thenReturn(applicationInfo)
-        context.setMockPackageManager(packageManager)
-
-        fakeClock = FakeSystemClock()
-        fakeExecutor = FakeExecutor(fakeClock)
-
-        uiEventLoggerFake = UiEventLoggerFake()
-        senderUiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
-
-        whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
-
-        controllerSender = TestMediaTttChipControllerSender(
-            commandQueue,
-            context,
-            logger,
-            windowManager,
-            fakeExecutor,
-            accessibilityManager,
-            configurationController,
-            powerManager,
-            senderUiEventLogger,
-            falsingManager,
-            falsingCollector,
-            viewUtil,
-        )
-        controllerSender.start()
-
-        val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
-        verify(commandQueue).addCallback(callbackCaptor.capture())
-        commandQueueCallback = callbackCaptor.value!!
-    }
-
-    @Test
-    fun commandQueueCallback_almostCloseToStartCast_triggersCorrectChip() {
-        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
-            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
-            routeInfo,
-            null
-        )
-
-        assertThat(getChipView().getChipText()).isEqualTo(
-            almostCloseToStartCast().state.getChipTextString(context, OTHER_DEVICE_NAME)
-        )
-        assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
-            MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST.id
-        )
-    }
-
-    @Test
-    fun commandQueueCallback_almostCloseToEndCast_triggersCorrectChip() {
-        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
-            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
-            routeInfo,
-            null
-        )
-
-        assertThat(getChipView().getChipText()).isEqualTo(
-            almostCloseToEndCast().state.getChipTextString(context, OTHER_DEVICE_NAME)
-        )
-        assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
-            MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST.id
-        )
-    }
-
-    @Test
-    fun commandQueueCallback_transferToReceiverTriggered_triggersCorrectChip() {
-        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
-            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
-            routeInfo,
-            null
-        )
-
-        assertThat(getChipView().getChipText()).isEqualTo(
-            transferToReceiverTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME)
-        )
-        assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
-            MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED.id
-        )
-    }
-
-    @Test
-    fun commandQueueCallback_transferToThisDeviceTriggered_triggersCorrectChip() {
-        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
-            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
-            routeInfo,
-            null
-        )
-
-        assertThat(getChipView().getChipText()).isEqualTo(
-            transferToThisDeviceTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME)
-        )
-        assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
-            MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED.id
-        )
-    }
-
-    @Test
-    fun commandQueueCallback_transferToReceiverSucceeded_triggersCorrectChip() {
-        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
-            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
-            routeInfo,
-            null
-        )
-
-        assertThat(getChipView().getChipText()).isEqualTo(
-            transferToReceiverSucceeded().state.getChipTextString(context, OTHER_DEVICE_NAME)
-        )
-        assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
-            MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id
-        )
-    }
-
-    @Test
-    fun commandQueueCallback_transferToThisDeviceSucceeded_triggersCorrectChip() {
-        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
-            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
-            routeInfo,
-            null
-        )
-
-        assertThat(getChipView().getChipText()).isEqualTo(
-            transferToThisDeviceSucceeded().state.getChipTextString(context, OTHER_DEVICE_NAME)
-        )
-        assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
-            MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id
-        )
-    }
-
-    @Test
-    fun commandQueueCallback_transferToReceiverFailed_triggersCorrectChip() {
-        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
-            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
-            routeInfo,
-            null
-        )
-
-        assertThat(getChipView().getChipText()).isEqualTo(
-            transferToReceiverFailed().state.getChipTextString(context, OTHER_DEVICE_NAME)
-        )
-        assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
-            MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id
-        )
-    }
-
-    @Test
-    fun commandQueueCallback_transferToThisDeviceFailed_triggersCorrectChip() {
-        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
-            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
-            routeInfo,
-            null
-        )
-
-        assertThat(getChipView().getChipText()).isEqualTo(
-            transferToThisDeviceFailed().state.getChipTextString(context, OTHER_DEVICE_NAME)
-        )
-        assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
-            MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id
-        )
-    }
-
-    @Test
-    fun commandQueueCallback_farFromReceiver_noChipShown() {
-        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
-            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
-            routeInfo,
-            null
-        )
-
-        verify(windowManager, never()).addView(any(), any())
-        assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
-            MediaTttSenderUiEvents.MEDIA_TTT_SENDER_FAR_FROM_RECEIVER.id
-        )
-    }
-
-    @Test
-    fun commandQueueCallback_almostCloseThenFarFromReceiver_chipShownThenHidden() {
-        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
-            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
-            routeInfo,
-            null
-        )
-
-        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
-            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
-            routeInfo,
-            null
-        )
-
-        val viewCaptor = ArgumentCaptor.forClass(View::class.java)
-        verify(windowManager).addView(viewCaptor.capture(), any())
-        verify(windowManager).removeView(viewCaptor.value)
-    }
-
-    @Test
-    fun commandQueueCallback_invalidStateParam_noChipShown() {
-        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
-            100,
-            routeInfo,
-            null
-        )
-
-        verify(windowManager, never()).addView(any(), any())
-    }
-
-    @Test
-    fun receivesNewStateFromCommandQueue_isLogged() {
-        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
-            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
-            routeInfo,
-            null
-        )
-
-        verify(logger).logStateChange(any(), any(), any())
-    }
-
-    @Test
-    fun almostCloseToStartCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() {
-        val state = almostCloseToStartCast()
-        controllerSender.displayView(state)
-
-        val chipView = getChipView()
-        assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
-        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
-        assertThat(chipView.getChipText()).isEqualTo(
-            state.state.getChipTextString(context, OTHER_DEVICE_NAME)
-        )
-        assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
-        assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
-        assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
-    }
-
-    @Test
-    fun almostCloseToEndCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() {
-        val state = almostCloseToEndCast()
-        controllerSender.displayView(state)
-
-        val chipView = getChipView()
-        assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
-        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
-        assertThat(chipView.getChipText()).isEqualTo(
-            state.state.getChipTextString(context, OTHER_DEVICE_NAME)
-        )
-        assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
-        assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
-        assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
-    }
-
-    @Test
-    fun transferToReceiverTriggered_appIcon_loadingIcon_noUndo_noFailureIcon() {
-        val state = transferToReceiverTriggered()
-        controllerSender.displayView(state)
-
-        val chipView = getChipView()
-        assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
-        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
-        assertThat(chipView.getChipText()).isEqualTo(
-            state.state.getChipTextString(context, OTHER_DEVICE_NAME)
-        )
-        assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
-        assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
-        assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
-    }
-
-    @Test
-    fun transferToThisDeviceTriggered_appIcon_loadingIcon_noUndo_noFailureIcon() {
-        val state = transferToThisDeviceTriggered()
-        controllerSender.displayView(state)
-
-        val chipView = getChipView()
-        assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
-        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
-        assertThat(chipView.getChipText()).isEqualTo(
-            state.state.getChipTextString(context, OTHER_DEVICE_NAME)
-        )
-        assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
-        assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
-        assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
-    }
-
-    @Test
-    fun transferToReceiverSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() {
-        val state = transferToReceiverSucceeded()
-        controllerSender.displayView(state)
-
-        val chipView = getChipView()
-        assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
-        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
-        assertThat(chipView.getChipText()).isEqualTo(
-            state.state.getChipTextString(context, OTHER_DEVICE_NAME)
-        )
-        assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
-        assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
-    }
-
-    @Test
-    fun transferToReceiverSucceeded_nullUndoRunnable_noUndo() {
-        controllerSender.displayView(transferToReceiverSucceeded(undoCallback = null))
-
-        val chipView = getChipView()
-        assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
-    }
-
-    @Test
-    fun transferToReceiverSucceeded_withUndoRunnable_undoWithClick() {
-        val undoCallback = object : IUndoMediaTransferCallback.Stub() {
-            override fun onUndoTriggered() {}
-        }
-        controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
-
-        val chipView = getChipView()
-        assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
-        assertThat(chipView.getUndoButton().hasOnClickListeners()).isTrue()
-    }
-
-    @Test
-    fun transferToReceiverSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() {
-        var undoCallbackCalled = false
-        val undoCallback = object : IUndoMediaTransferCallback.Stub() {
-            override fun onUndoTriggered() {
-                undoCallbackCalled = true
-            }
-        }
-
-        controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
-        getChipView().getUndoButton().performClick()
-
-        assertThat(undoCallbackCalled).isTrue()
-    }
-
-    @Test
-    fun transferToReceiverSucceeded_withUndoRunnable_falseTap_callbackNotRun() {
-        whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true)
-        var undoCallbackCalled = false
-        val undoCallback = object : IUndoMediaTransferCallback.Stub() {
-            override fun onUndoTriggered() {
-                undoCallbackCalled = true
-            }
-        }
-
-        controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
-        getChipView().getUndoButton().performClick()
-
-        assertThat(undoCallbackCalled).isFalse()
-    }
-
-    @Test
-    fun transferToReceiverSucceeded_withUndoRunnable_realTap_callbackRun() {
-        whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false)
-        var undoCallbackCalled = false
-        val undoCallback = object : IUndoMediaTransferCallback.Stub() {
-            override fun onUndoTriggered() {
-                undoCallbackCalled = true
-            }
-        }
-
-        controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
-        getChipView().getUndoButton().performClick()
-
-        assertThat(undoCallbackCalled).isTrue()
-    }
-
-    @Test
-    fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
-        val undoCallback = object : IUndoMediaTransferCallback.Stub() {
-            override fun onUndoTriggered() {}
-        }
-        controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
-
-        getChipView().getUndoButton().performClick()
-
-        assertThat(getChipView().getChipText()).isEqualTo(
-            transferToThisDeviceTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME)
-        )
-        assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
-            MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED.id
-        )
-    }
-
-    @Test
-    fun transferToThisDeviceSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() {
-        val state = transferToThisDeviceSucceeded()
-        controllerSender.displayView(state)
-
-        val chipView = getChipView()
-        assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
-        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
-        assertThat(chipView.getChipText()).isEqualTo(
-            state.state.getChipTextString(context, OTHER_DEVICE_NAME)
-        )
-        assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
-        assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
-    }
-
-    @Test
-    fun transferToThisDeviceSucceeded_nullUndoRunnable_noUndo() {
-        controllerSender.displayView(transferToThisDeviceSucceeded(undoCallback = null))
-
-        val chipView = getChipView()
-        assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
-    }
-
-    @Test
-    fun transferToThisDeviceSucceeded_withUndoRunnable_undoWithClick() {
-        val undoCallback = object : IUndoMediaTransferCallback.Stub() {
-            override fun onUndoTriggered() {}
-        }
-        controllerSender.displayView(transferToThisDeviceSucceeded(undoCallback))
-
-        val chipView = getChipView()
-        assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
-        assertThat(chipView.getUndoButton().hasOnClickListeners()).isTrue()
-    }
-
-    @Test
-    fun transferToThisDeviceSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() {
-        var undoCallbackCalled = false
-        val undoCallback = object : IUndoMediaTransferCallback.Stub() {
-            override fun onUndoTriggered() {
-                undoCallbackCalled = true
-            }
-        }
-
-        controllerSender.displayView(transferToThisDeviceSucceeded(undoCallback))
-        getChipView().getUndoButton().performClick()
-
-        assertThat(undoCallbackCalled).isTrue()
-    }
-
-    @Test
-    fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToReceiverTriggered() {
-        val undoCallback = object : IUndoMediaTransferCallback.Stub() {
-            override fun onUndoTriggered() {}
-        }
-        controllerSender.displayView(transferToThisDeviceSucceeded(undoCallback))
-
-        getChipView().getUndoButton().performClick()
-
-        assertThat(getChipView().getChipText()).isEqualTo(
-            transferToReceiverTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME)
-        )
-        assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
-            MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED.id
-        )
-    }
-
-    @Test
-    fun transferToReceiverFailed_appIcon_noDeviceName_noLoadingIcon_noUndo_failureIcon() {
-        val state = transferToReceiverFailed()
-        controllerSender.displayView(state)
-
-        val chipView = getChipView()
-        assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
-        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
-        assertThat(getChipView().getChipText()).isEqualTo(
-            state.state.getChipTextString(context, OTHER_DEVICE_NAME)
-        )
-        assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
-        assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
-        assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.VISIBLE)
-    }
-
-    @Test
-    fun transferToThisDeviceFailed_appIcon_noDeviceName_noLoadingIcon_noUndo_failureIcon() {
-        val state = transferToThisDeviceFailed()
-        controllerSender.displayView(state)
-
-        val chipView = getChipView()
-        assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
-        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
-        assertThat(getChipView().getChipText()).isEqualTo(
-            state.state.getChipTextString(context, OTHER_DEVICE_NAME)
-        )
-        assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
-        assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
-        assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.VISIBLE)
-    }
-
-    @Test
-    fun changeFromAlmostCloseToStartToTransferTriggered_loadingIconAppears() {
-        controllerSender.displayView(almostCloseToStartCast())
-        controllerSender.displayView(transferToReceiverTriggered())
-
-        assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
-    }
-
-    @Test
-    fun changeFromTransferTriggeredToTransferSucceeded_loadingIconDisappears() {
-        controllerSender.displayView(transferToReceiverTriggered())
-        controllerSender.displayView(transferToReceiverSucceeded())
-
-        assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.GONE)
-    }
-
-    @Test
-    fun changeFromTransferTriggeredToTransferSucceeded_undoButtonAppears() {
-        controllerSender.displayView(transferToReceiverTriggered())
-        controllerSender.displayView(
-            transferToReceiverSucceeded(
-                object : IUndoMediaTransferCallback.Stub() {
-                    override fun onUndoTriggered() {}
-                }
-            )
-        )
-
-        assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.VISIBLE)
-    }
-
-    @Test
-    fun changeFromTransferSucceededToAlmostCloseToStart_undoButtonDisappears() {
-        controllerSender.displayView(transferToReceiverSucceeded())
-        controllerSender.displayView(almostCloseToStartCast())
-
-        assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE)
-    }
-
-    @Test
-    fun changeFromTransferTriggeredToTransferFailed_failureIconAppears() {
-        controllerSender.displayView(transferToReceiverTriggered())
-        controllerSender.displayView(transferToReceiverFailed())
-
-        assertThat(getChipView().getFailureIcon().visibility).isEqualTo(View.VISIBLE)
-    }
-
-    @Test
-    fun transferToReceiverTriggeredThenRemoveView_viewStillDisplayed() {
-        controllerSender.displayView(transferToReceiverTriggered())
-        fakeClock.advanceTime(1000L)
-
-        controllerSender.removeView("fakeRemovalReason")
-        fakeExecutor.runAllReady()
-
-        verify(windowManager, never()).removeView(any())
-        verify(logger).logRemovalBypass(any(), any())
-    }
-
-    @Test
-    fun transferToReceiverTriggeredThenFarFromReceiver_viewStillDisplayed() {
-        controllerSender.displayView(transferToReceiverTriggered())
-
-        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
-            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
-            routeInfo,
-            null
-        )
-        fakeExecutor.runAllReady()
-
-        verify(windowManager, never()).removeView(any())
-        verify(logger).logRemovalBypass(any(), any())
-    }
-
-    @Test
-    fun transferToReceiverTriggeredThenRemoveView_eventuallyTimesOut() {
-        controllerSender.displayView(transferToReceiverTriggered())
-
-        controllerSender.removeView("fakeRemovalReason")
-        fakeClock.advanceTime(TIMEOUT + 1L)
-
-        verify(windowManager).removeView(any())
-    }
-
-    @Test
-    fun transferToThisDeviceTriggeredThenRemoveView_viewStillDisplayed() {
-        controllerSender.displayView(transferToThisDeviceTriggered())
-        fakeClock.advanceTime(1000L)
-
-        controllerSender.removeView("fakeRemovalReason")
-        fakeExecutor.runAllReady()
-
-        verify(windowManager, never()).removeView(any())
-        verify(logger).logRemovalBypass(any(), any())
-    }
-
-    @Test
-    fun transferToThisDeviceTriggeredThenRemoveView_eventuallyTimesOut() {
-        controllerSender.displayView(transferToThisDeviceTriggered())
-
-        controllerSender.removeView("fakeRemovalReason")
-        fakeClock.advanceTime(TIMEOUT + 1L)
-
-        verify(windowManager).removeView(any())
-    }
-
-    @Test
-    fun transferToThisDeviceTriggeredThenFarFromReceiver_viewStillDisplayed() {
-        controllerSender.displayView(transferToThisDeviceTriggered())
-
-        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
-            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
-            routeInfo,
-            null
-        )
-        fakeExecutor.runAllReady()
-
-        verify(windowManager, never()).removeView(any())
-        verify(logger).logRemovalBypass(any(), any())
-    }
-
-    @Test
-    fun transferToReceiverSucceededThenRemoveView_viewStillDisplayed() {
-        controllerSender.displayView(transferToReceiverSucceeded())
-
-        controllerSender.removeView("fakeRemovalReason")
-        fakeExecutor.runAllReady()
-
-        verify(windowManager, never()).removeView(any())
-        verify(logger).logRemovalBypass(any(), any())
-    }
-
-    @Test
-    fun transferToReceiverSucceededThenRemoveView_eventuallyTimesOut() {
-        controllerSender.displayView(transferToReceiverSucceeded())
-
-        controllerSender.removeView("fakeRemovalReason")
-        fakeClock.advanceTime(TIMEOUT + 1L)
-
-        verify(windowManager).removeView(any())
-    }
-
-    @Test
-    fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayed() {
-        controllerSender.displayView(transferToReceiverSucceeded())
-
-        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
-            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
-            routeInfo,
-            null
-        )
-        fakeExecutor.runAllReady()
-
-        verify(windowManager, never()).removeView(any())
-        verify(logger).logRemovalBypass(any(), any())
-    }
-
-    @Test
-    fun transferToThisDeviceSucceededThenRemoveView_viewStillDisplayed() {
-        controllerSender.displayView(transferToThisDeviceSucceeded())
-
-        controllerSender.removeView("fakeRemovalReason")
-        fakeExecutor.runAllReady()
-
-        verify(windowManager, never()).removeView(any())
-        verify(logger).logRemovalBypass(any(), any())
-    }
-
-    @Test
-    fun transferToThisDeviceSucceededThenRemoveView_eventuallyTimesOut() {
-        controllerSender.displayView(transferToThisDeviceSucceeded())
-
-        controllerSender.removeView("fakeRemovalReason")
-        fakeClock.advanceTime(TIMEOUT + 1L)
-
-        verify(windowManager).removeView(any())
-    }
-
-    @Test
-    fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayed() {
-        controllerSender.displayView(transferToThisDeviceSucceeded())
-
-        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
-            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
-            routeInfo,
-            null
-        )
-        fakeExecutor.runAllReady()
-
-        verify(windowManager, never()).removeView(any())
-        verify(logger).logRemovalBypass(any(), any())
-    }
-
-    private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
-
-    private fun ViewGroup.getChipText(): String =
-        (this.requireViewById<TextView>(R.id.text)).text as String
-
-    private fun ViewGroup.getLoadingIconVisibility(): Int =
-        this.requireViewById<View>(R.id.loading).visibility
-
-    private fun ViewGroup.getUndoButton(): View = this.requireViewById(R.id.undo)
-
-    private fun ViewGroup.getFailureIcon(): View = this.requireViewById(R.id.failure_icon)
-
-    private fun getChipView(): ViewGroup {
-        val viewCaptor = ArgumentCaptor.forClass(View::class.java)
-        verify(windowManager).addView(viewCaptor.capture(), any())
-        return viewCaptor.value as ViewGroup
-    }
-
-    /** Helper method providing default parameters to not clutter up the tests. */
-    private fun almostCloseToStartCast() =
-        ChipSenderInfo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST, routeInfo)
-
-    /** Helper method providing default parameters to not clutter up the tests. */
-    private fun almostCloseToEndCast() =
-        ChipSenderInfo(ChipStateSender.ALMOST_CLOSE_TO_END_CAST, routeInfo)
-
-    /** Helper method providing default parameters to not clutter up the tests. */
-    private fun transferToReceiverTriggered() =
-        ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED, routeInfo)
-
-    /** Helper method providing default parameters to not clutter up the tests. */
-    private fun transferToThisDeviceTriggered() =
-        ChipSenderInfo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED, routeInfo)
-
-    /** Helper method providing default parameters to not clutter up the tests. */
-    private fun transferToReceiverSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
-        ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, undoCallback)
-
-    /** Helper method providing default parameters to not clutter up the tests. */
-    private fun transferToThisDeviceSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
-        ChipSenderInfo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED, routeInfo, undoCallback)
-
-    /** Helper method providing default parameters to not clutter up the tests. */
-    private fun transferToReceiverFailed() =
-        ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED, routeInfo)
-
-    /** Helper method providing default parameters to not clutter up the tests. */
-    private fun transferToThisDeviceFailed() =
-        ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED, routeInfo)
-
-    private class TestMediaTttChipControllerSender(
-        commandQueue: CommandQueue,
-        context: Context,
-        @MediaTttReceiverLogger logger: MediaTttLogger,
-        windowManager: WindowManager,
-        mainExecutor: DelayableExecutor,
-        accessibilityManager: AccessibilityManager,
-        configurationController: ConfigurationController,
-        powerManager: PowerManager,
-        uiEventLogger: MediaTttSenderUiEventLogger,
-        falsingManager: FalsingManager,
-        falsingCollector: FalsingCollector,
-        viewUtil: ViewUtil,
-    ) : MediaTttChipControllerSender(
-        commandQueue,
-        context,
-        logger,
-        windowManager,
-        mainExecutor,
-        accessibilityManager,
-        configurationController,
-        powerManager,
-        uiEventLogger,
-        falsingManager,
-        falsingCollector,
-        viewUtil,
-    ) {
-        override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
-            // Just bypass the animation in tests
-            onAnimationEnd.run()
-        }
-    }
-}
-
-private const val APP_NAME = "Fake app name"
-private const val OTHER_DEVICE_NAME = "My Tablet"
-private const val PACKAGE_NAME = "com.android.systemui"
-private const val TIMEOUT = 10000
-
-private val routeInfo = MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME)
-    .addFeature("feature")
-    .setClientPackageName(PACKAGE_NAME)
-    .build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
new file mode 100644
index 0000000..fdeb3f5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -0,0 +1,691 @@
+/*
+ * 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.media.taptotransfer.sender
+
+import android.app.StatusBarManager
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import android.media.MediaRoute2Info
+import android.os.PowerManager
+import android.os.VibrationEffect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.statusbar.IUndoMediaTransferCallback
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.common.shared.model.Text.Companion.loadText
+import com.android.systemui.media.taptotransfer.MediaTttFlags
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import com.android.systemui.temporarydisplay.chipbar.FakeChipbarCoordinator
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.view.ViewUtil
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class MediaTttSenderCoordinatorTest : SysuiTestCase() {
+
+    // Note: This tests are a bit like integration tests because they use a real instance of
+    //   [ChipbarCoordinator] and verify that the coordinator displays the correct view, based on
+    //   the inputs from [MediaTttSenderCoordinator].
+
+    private lateinit var underTest: MediaTttSenderCoordinator
+
+    @Mock private lateinit var accessibilityManager: AccessibilityManager
+    @Mock private lateinit var applicationInfo: ApplicationInfo
+    @Mock private lateinit var commandQueue: CommandQueue
+    @Mock private lateinit var configurationController: ConfigurationController
+    @Mock private lateinit var falsingManager: FalsingManager
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var logger: MediaTttLogger
+    @Mock private lateinit var mediaTttFlags: MediaTttFlags
+    @Mock private lateinit var packageManager: PackageManager
+    @Mock private lateinit var powerManager: PowerManager
+    @Mock private lateinit var viewUtil: ViewUtil
+    @Mock private lateinit var windowManager: WindowManager
+    @Mock private lateinit var vibratorHelper: VibratorHelper
+    private lateinit var chipbarCoordinator: ChipbarCoordinator
+    private lateinit var commandQueueCallback: CommandQueue.Callbacks
+    private lateinit var fakeAppIconDrawable: Drawable
+    private lateinit var fakeClock: FakeSystemClock
+    private lateinit var fakeExecutor: FakeExecutor
+    private lateinit var uiEventLoggerFake: UiEventLoggerFake
+    private lateinit var uiEventLogger: MediaTttSenderUiEventLogger
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true)
+        whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
+
+        fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
+        whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME)
+        whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
+        whenever(
+                packageManager.getApplicationInfo(
+                    eq(PACKAGE_NAME),
+                    any<PackageManager.ApplicationInfoFlags>()
+                )
+            )
+            .thenReturn(applicationInfo)
+        context.setMockPackageManager(packageManager)
+
+        fakeClock = FakeSystemClock()
+        fakeExecutor = FakeExecutor(fakeClock)
+
+        uiEventLoggerFake = UiEventLoggerFake()
+        uiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
+
+        chipbarCoordinator =
+            FakeChipbarCoordinator(
+                context,
+                logger,
+                windowManager,
+                fakeExecutor,
+                accessibilityManager,
+                configurationController,
+                powerManager,
+                falsingManager,
+                falsingCollector,
+                viewUtil,
+                vibratorHelper,
+            )
+        chipbarCoordinator.start()
+
+        underTest =
+            MediaTttSenderCoordinator(
+                chipbarCoordinator,
+                commandQueue,
+                context,
+                logger,
+                mediaTttFlags,
+                uiEventLogger,
+            )
+        underTest.start()
+
+        val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
+        verify(commandQueue).addCallback(callbackCaptor.capture())
+        commandQueueCallback = callbackCaptor.value!!
+    }
+
+    @Test
+    fun commandQueueCallback_flagOff_noCallbackAdded() {
+        reset(commandQueue)
+        whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(false)
+        underTest =
+            MediaTttSenderCoordinator(
+                chipbarCoordinator,
+                commandQueue,
+                context,
+                logger,
+                mediaTttFlags,
+                uiEventLogger,
+            )
+        underTest.start()
+
+        verify(commandQueue, never()).addCallback(any())
+    }
+
+    @Test
+    fun commandQueueCallback_almostCloseToStartCast_triggersCorrectChip() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+            routeInfo,
+            null
+        )
+
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+        assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+        assertThat(chipbarView.getChipText())
+            .isEqualTo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST.getExpectedStateText())
+        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
+        assertThat(uiEventLoggerFake.eventId(0))
+            .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST.id)
+        verify(vibratorHelper).vibrate(any<VibrationEffect>())
+    }
+
+    @Test
+    fun commandQueueCallback_almostCloseToEndCast_triggersCorrectChip() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+            routeInfo,
+            null
+        )
+
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+        assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+        assertThat(chipbarView.getChipText())
+            .isEqualTo(ChipStateSender.ALMOST_CLOSE_TO_END_CAST.getExpectedStateText())
+        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
+        assertThat(uiEventLoggerFake.eventId(0))
+            .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST.id)
+        verify(vibratorHelper).vibrate(any<VibrationEffect>())
+    }
+
+    @Test
+    fun commandQueueCallback_transferToReceiverTriggered_triggersCorrectChip() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+            routeInfo,
+            null
+        )
+
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+        assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+        assertThat(chipbarView.getChipText())
+            .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText())
+        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
+        assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
+        assertThat(uiEventLoggerFake.eventId(0))
+            .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED.id)
+        verify(vibratorHelper).vibrate(any<VibrationEffect>())
+    }
+
+    @Test
+    fun commandQueueCallback_transferToThisDeviceTriggered_triggersCorrectChip() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+            routeInfo,
+            null
+        )
+
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+        assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+        assertThat(chipbarView.getChipText())
+            .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText())
+        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
+        assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
+        assertThat(uiEventLoggerFake.eventId(0))
+            .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED.id)
+        verify(vibratorHelper).vibrate(any<VibrationEffect>())
+    }
+
+    @Test
+    fun commandQueueCallback_transferToReceiverSucceeded_triggersCorrectChip() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+            routeInfo,
+            null
+        )
+
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+        assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+        assertThat(chipbarView.getChipText())
+            .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText())
+        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+        assertThat(uiEventLoggerFake.eventId(0))
+            .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id)
+        verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
+    }
+
+    @Test
+    fun transferToReceiverSucceeded_nullUndoCallback_noUndo() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+            routeInfo,
+            /* undoCallback= */ null
+        )
+
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+    }
+
+    @Test
+    fun transferToReceiverSucceeded_withUndoRunnable_undoVisible() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+            routeInfo,
+            /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
+                override fun onUndoTriggered() {}
+            },
+        )
+
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
+        assertThat(chipbarView.getUndoButton().hasOnClickListeners()).isTrue()
+    }
+
+    @Test
+    fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
+        var undoCallbackCalled = false
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+            routeInfo,
+            /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
+                override fun onUndoTriggered() {
+                    undoCallbackCalled = true
+                }
+            },
+        )
+
+        getChipbarView().getUndoButton().performClick()
+
+        // Event index 1 since initially displaying the succeeded chip would also log an event
+        assertThat(uiEventLoggerFake.eventId(1))
+            .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED.id)
+        assertThat(undoCallbackCalled).isTrue()
+        assertThat(getChipbarView().getChipText())
+            .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText())
+    }
+
+    @Test
+    fun commandQueueCallback_transferToThisDeviceSucceeded_triggersCorrectChip() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+            routeInfo,
+            null
+        )
+
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+        assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+        assertThat(chipbarView.getChipText())
+            .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText())
+        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+        assertThat(uiEventLoggerFake.eventId(0))
+            .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id)
+        verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
+    }
+
+    @Test
+    fun transferToThisDeviceSucceeded_nullUndoCallback_noUndo() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+            routeInfo,
+            /* undoCallback= */ null
+        )
+
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+    }
+
+    @Test
+    fun transferToThisDeviceSucceeded_withUndoRunnable_undoVisible() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+            routeInfo,
+            /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
+                override fun onUndoTriggered() {}
+            },
+        )
+
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
+        assertThat(chipbarView.getUndoButton().hasOnClickListeners()).isTrue()
+    }
+
+    @Test
+    fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
+        var undoCallbackCalled = false
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+            routeInfo,
+            /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
+                override fun onUndoTriggered() {
+                    undoCallbackCalled = true
+                }
+            },
+        )
+
+        getChipbarView().getUndoButton().performClick()
+
+        // Event index 1 since initially displaying the succeeded chip would also log an event
+        assertThat(uiEventLoggerFake.eventId(1))
+            .isEqualTo(
+                MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED.id
+            )
+        assertThat(undoCallbackCalled).isTrue()
+        assertThat(getChipbarView().getChipText())
+            .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText())
+    }
+
+    @Test
+    fun commandQueueCallback_transferToReceiverFailed_triggersCorrectChip() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
+            routeInfo,
+            null
+        )
+
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+        assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+        assertThat(chipbarView.getChipText())
+            .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED.getExpectedStateText())
+        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
+        assertThat(uiEventLoggerFake.eventId(0))
+            .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id)
+        verify(vibratorHelper).vibrate(any<VibrationEffect>())
+    }
+
+    @Test
+    fun commandQueueCallback_transferToThisDeviceFailed_triggersCorrectChip() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
+            routeInfo,
+            null
+        )
+
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+        assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+        assertThat(chipbarView.getChipText())
+            .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED.getExpectedStateText())
+        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
+        assertThat(uiEventLoggerFake.eventId(0))
+            .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id)
+        verify(vibratorHelper).vibrate(any<VibrationEffect>())
+    }
+
+    @Test
+    fun commandQueueCallback_farFromReceiver_noChipShown() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+            routeInfo,
+            null
+        )
+
+        verify(windowManager, never()).addView(any(), any())
+        assertThat(uiEventLoggerFake.eventId(0))
+            .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_FAR_FROM_RECEIVER.id)
+    }
+
+    @Test
+    fun commandQueueCallback_almostCloseThenFarFromReceiver_chipShownThenHidden() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+            routeInfo,
+            null
+        )
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+            routeInfo,
+            null
+        )
+
+        val viewCaptor = ArgumentCaptor.forClass(View::class.java)
+        verify(windowManager).addView(viewCaptor.capture(), any())
+        verify(windowManager).removeView(viewCaptor.value)
+    }
+
+    @Test
+    fun commandQueueCallback_invalidStateParam_noChipShown() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(100, routeInfo, null)
+
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun receivesNewStateFromCommandQueue_isLogged() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logStateChange(any(), any(), any())
+    }
+
+    @Test
+    fun transferToReceiverTriggeredThenFarFromReceiver_viewStillDisplayedButStillTimesOut() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+            routeInfo,
+            null
+        )
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+            routeInfo,
+            null
+        )
+        fakeExecutor.runAllReady()
+
+        verify(windowManager, never()).removeView(any())
+        verify(logger).logRemovalBypass(any(), any())
+
+        fakeClock.advanceTime(TIMEOUT + 1L)
+
+        verify(windowManager).removeView(any())
+    }
+
+    @Test
+    fun transferToThisDeviceTriggeredThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+            routeInfo,
+            null
+        )
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+            routeInfo,
+            null
+        )
+        fakeExecutor.runAllReady()
+
+        verify(windowManager, never()).removeView(any())
+        verify(logger).logRemovalBypass(any(), any())
+
+        fakeClock.advanceTime(TIMEOUT + 1L)
+
+        verify(windowManager).removeView(any())
+    }
+
+    @Test
+    fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+            routeInfo,
+            null
+        )
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+            routeInfo,
+            null
+        )
+        fakeExecutor.runAllReady()
+
+        verify(windowManager, never()).removeView(any())
+        verify(logger).logRemovalBypass(any(), any())
+
+        fakeClock.advanceTime(TIMEOUT + 1L)
+
+        verify(windowManager).removeView(any())
+    }
+
+    @Test
+    fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+            routeInfo,
+            null
+        )
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+            routeInfo,
+            null
+        )
+        fakeExecutor.runAllReady()
+
+        verify(windowManager, never()).removeView(any())
+        verify(logger).logRemovalBypass(any(), any())
+
+        fakeClock.advanceTime(TIMEOUT + 1L)
+
+        verify(windowManager).removeView(any())
+    }
+
+    @Test
+    fun transferToReceiverSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+            routeInfo,
+            object : IUndoMediaTransferCallback.Stub() {
+                override fun onUndoTriggered() {}
+            },
+        )
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getChipText())
+            .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText())
+
+        // Because [MediaTttSenderCoordinator] internally creates the undo callback, we should
+        // verify that the new state it triggers operates just like any other state.
+        getChipbarView().getUndoButton().performClick()
+        fakeExecutor.runAllReady()
+
+        // Verify that the click updated us to the triggered state
+        assertThat(chipbarView.getChipText())
+            .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText())
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+            routeInfo,
+            null
+        )
+        fakeExecutor.runAllReady()
+
+        // Verify that we didn't remove the chipbar because it's in the triggered state
+        verify(windowManager, never()).removeView(any())
+        verify(logger).logRemovalBypass(any(), any())
+
+        fakeClock.advanceTime(TIMEOUT + 1L)
+
+        // Verify we eventually remove the chipbar
+        verify(windowManager).removeView(any())
+    }
+
+    @Test
+    fun transferToThisDeviceSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+            routeInfo,
+            object : IUndoMediaTransferCallback.Stub() {
+                override fun onUndoTriggered() {}
+            },
+        )
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getChipText())
+            .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText())
+
+        // Because [MediaTttSenderCoordinator] internally creates the undo callback, we should
+        // verify that the new state it triggers operates just like any other state.
+        getChipbarView().getUndoButton().performClick()
+        fakeExecutor.runAllReady()
+
+        // Verify that the click updated us to the triggered state
+        assertThat(chipbarView.getChipText())
+            .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText())
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+            routeInfo,
+            null
+        )
+        fakeExecutor.runAllReady()
+
+        // Verify that we didn't remove the chipbar because it's in the triggered state
+        verify(windowManager, never()).removeView(any())
+        verify(logger).logRemovalBypass(any(), any())
+
+        fakeClock.advanceTime(TIMEOUT + 1L)
+
+        // Verify we eventually remove the chipbar
+        verify(windowManager).removeView(any())
+    }
+
+    private fun getChipbarView(): ViewGroup {
+        val viewCaptor = ArgumentCaptor.forClass(View::class.java)
+        verify(windowManager).addView(viewCaptor.capture(), any())
+        return viewCaptor.value as ViewGroup
+    }
+
+    private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.start_icon)
+
+    private fun ViewGroup.getChipText(): String =
+        (this.requireViewById<TextView>(R.id.text)).text as String
+
+    private fun ViewGroup.getLoadingIcon(): View = this.requireViewById(R.id.loading)
+
+    private fun ViewGroup.getErrorIcon(): View = this.requireViewById(R.id.error)
+
+    private fun ViewGroup.getUndoButton(): View = this.requireViewById(R.id.end_button)
+
+    private fun ChipStateSender.getExpectedStateText(): String? {
+        return this.getChipTextString(context, OTHER_DEVICE_NAME).loadText(context)
+    }
+}
+
+private const val APP_NAME = "Fake app name"
+private const val OTHER_DEVICE_NAME = "My Tablet"
+private const val PACKAGE_NAME = "com.android.systemui"
+private const val TIMEOUT = 10000
+
+private val routeInfo =
+    MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME)
+        .addFeature("feature")
+        .setClientPackageName(PACKAGE_NAME)
+        .build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
index 0badd861..1bc4719 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
@@ -147,6 +147,18 @@
     }
 
     @Test
+    public void testMonochromatic() {
+        int colorInt = 0xffB3588A; // H350 C50 T50
+        ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
+                Style.MONOCHROMATIC /* style */);
+        int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
+        Assert.assertTrue(
+                Color.red(neutralMid) == Color.green(neutralMid)
+                && Color.green(neutralMid) == Color.blue(neutralMid)
+        );
+    }
+
+    @Test
     @SuppressWarnings("ResultOfMethodCallIgnored")
     public void testToString() {
         new ColorScheme(Color.TRANSPARENT, false /* darkTheme */).toString();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
index e2c6ff9..d6db62a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.plugins.log.LogcatEchoTracker
 import com.android.systemui.statusbar.DisableFlagsLogger
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index d2c2d58..cd7a949 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -50,7 +50,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.media.MediaHost;
+import com.android.systemui.media.controls.ui.MediaHost;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSFragmentComponent;
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 b847ad0..caf8321 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -44,7 +44,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaHost;
+import com.android.systemui.media.controls.ui.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/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index e539705..3c867ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -7,8 +7,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.media.MediaHost
-import com.android.systemui.media.MediaHostState
+import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.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/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 1c686c6..5e9c1aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -22,7 +22,6 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -52,6 +51,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.FrameLayout;
 import android.widget.TextView;
 
 import com.android.systemui.R;
@@ -97,6 +97,7 @@
     private static final int DEFAULT_ICON_ID = R.drawable.ic_info_outline;
 
     private ViewGroup mRootView;
+    private ViewGroup mSecurityFooterView;
     private TextView mFooterText;
     private TestableImageView mPrimaryFooterIcon;
     private QSSecurityFooter mFooter;
@@ -121,21 +122,26 @@
         Looper looper = mTestableLooper.getLooper();
         Handler mainHandler = new Handler(looper);
         when(mUserTracker.getUserInfo()).thenReturn(mock(UserInfo.class));
-        mRootView = (ViewGroup) new LayoutInflaterBuilder(mContext)
+        mSecurityFooterView = (ViewGroup) new LayoutInflaterBuilder(mContext)
                 .replace("ImageView", TestableImageView.class)
                 .build().inflate(R.layout.quick_settings_security_footer, null, false);
         mFooterUtils = new QSSecurityFooterUtils(getContext(),
                 getContext().getSystemService(DevicePolicyManager.class), mUserTracker,
                 mainHandler, mActivityStarter, mSecurityController, looper, mDialogLaunchAnimator);
-        mFooter = new QSSecurityFooter(mRootView, mainHandler, mSecurityController, looper,
-                mBroadcastDispatcher, mFooterUtils);
-        mFooterText = mRootView.findViewById(R.id.footer_text);
-        mPrimaryFooterIcon = mRootView.findViewById(R.id.primary_footer_icon);
+        mFooter = new QSSecurityFooter(mSecurityFooterView, mainHandler, mSecurityController,
+                looper, mBroadcastDispatcher, mFooterUtils);
+        mFooterText = mSecurityFooterView.findViewById(R.id.footer_text);
+        mPrimaryFooterIcon = mSecurityFooterView.findViewById(R.id.primary_footer_icon);
 
         when(mSecurityController.getDeviceOwnerComponentOnAnyUser())
                 .thenReturn(DEVICE_OWNER_COMPONENT);
         when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
                 .thenReturn(DEVICE_OWNER_TYPE_DEFAULT);
+
+        // mSecurityFooterView must have a ViewGroup parent so that
+        // DialogLaunchAnimator.Controller.fromView() does not return null.
+        mRootView = new FrameLayout(mContext);
+        mRootView.addView(mSecurityFooterView);
         ViewUtils.attachView(mRootView);
 
         mFooter.init();
@@ -153,7 +159,7 @@
         mFooter.refreshState();
 
         TestableLooper.get(this).processAllMessages();
-        assertEquals(View.GONE, mRootView.getVisibility());
+        assertEquals(View.GONE, mSecurityFooterView.getVisibility());
     }
 
     @Test
@@ -165,7 +171,7 @@
         TestableLooper.get(this).processAllMessages();
         assertEquals(mContext.getString(R.string.quick_settings_disclosure_management),
                      mFooterText.getText());
-        assertEquals(View.VISIBLE, mRootView.getVisibility());
+        assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility());
         assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
         assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
     }
@@ -181,7 +187,7 @@
         assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_management,
                                         MANAGING_ORGANIZATION),
                 mFooterText.getText());
-        assertEquals(View.VISIBLE, mRootView.getVisibility());
+        assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility());
         assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
         assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
     }
@@ -200,7 +206,7 @@
         assertEquals(mContext.getString(
                 R.string.quick_settings_financed_disclosure_named_management,
                 MANAGING_ORGANIZATION), mFooterText.getText());
-        assertEquals(View.VISIBLE, mRootView.getVisibility());
+        assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility());
         assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
         assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
     }
@@ -217,7 +223,7 @@
         mFooter.refreshState();
 
         TestableLooper.get(this).processAllMessages();
-        assertEquals(View.GONE, mRootView.getVisibility());
+        assertEquals(View.GONE, mSecurityFooterView.getVisibility());
     }
 
     @Test
@@ -227,8 +233,8 @@
         mFooter.refreshState();
 
         TestableLooper.get(this).processAllMessages();
-        assertFalse(mRootView.isClickable());
-        assertEquals(View.GONE, mRootView.findViewById(R.id.footer_icon).getVisibility());
+        assertFalse(mSecurityFooterView.isClickable());
+        assertEquals(View.GONE, mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility());
     }
 
     @Test
@@ -241,8 +247,9 @@
         mFooter.refreshState();
 
         TestableLooper.get(this).processAllMessages();
-        assertTrue(mRootView.isClickable());
-        assertEquals(View.VISIBLE, mRootView.findViewById(R.id.footer_icon).getVisibility());
+        assertTrue(mSecurityFooterView.isClickable());
+        assertEquals(View.VISIBLE,
+                mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility());
     }
 
     @Test
@@ -254,8 +261,8 @@
         mFooter.refreshState();
 
         TestableLooper.get(this).processAllMessages();
-        assertFalse(mRootView.isClickable());
-        assertEquals(View.GONE, mRootView.findViewById(R.id.footer_icon).getVisibility());
+        assertFalse(mSecurityFooterView.isClickable());
+        assertEquals(View.GONE, mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility());
     }
 
     @Test
@@ -734,11 +741,11 @@
     @Test
     public void testDialogUsesDialogLauncher() {
         when(mSecurityController.isDeviceManaged()).thenReturn(true);
-        mFooter.onClick(mRootView);
+        mFooter.onClick(mSecurityFooterView);
 
         mTestableLooper.processAllMessages();
 
-        verify(mDialogLaunchAnimator).showFromView(any(), eq(mRootView), any());
+        verify(mDialogLaunchAnimator).show(any(), any());
     }
 
     @Test
@@ -775,7 +782,7 @@
         ArgumentCaptor<AlertDialog> dialogCaptor = ArgumentCaptor.forClass(AlertDialog.class);
 
         mTestableLooper.processAllMessages();
-        verify(mDialogLaunchAnimator).showFromView(dialogCaptor.capture(), any(), any());
+        verify(mDialogLaunchAnimator).show(dialogCaptor.capture(), any());
 
         AlertDialog dialog = dialogCaptor.getValue();
         dialog.create();
@@ -817,8 +824,8 @@
         verify(mBroadcastDispatcher).registerReceiverWithHandler(captor.capture(), any(), any(),
                 any());
 
-        // Pretend view is not visible temporarily
-        mRootView.onVisibilityAggregated(false);
+        // Pretend view is not attached anymore.
+        mRootView.removeView(mSecurityFooterView);
         captor.getValue().onReceive(mContext,
                 new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG));
         mTestableLooper.processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 3c58b6fc..c452872 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -52,6 +52,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.dump.nano.SystemUIProtoDump;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.qs.QSTile;
@@ -114,8 +115,6 @@
     @Mock
     private DumpManager mDumpManager;
     @Mock
-    private QSTile.State mMockState;
-    @Mock
     private CentralSurfaces mCentralSurfaces;
     @Mock
     private QSLogger mQSLogger;
@@ -195,7 +194,6 @@
     }
 
     private void setUpTileFactory() {
-        when(mMockState.toString()).thenReturn(MOCK_STATE_STRING);
         // Only create this kind of tiles
         when(mDefaultFactory.createTile(anyString())).thenAnswer(
                 invocation -> {
@@ -209,7 +207,11 @@
                     } else if ("na".equals(spec)) {
                         return new NotAvailableTile(mQSTileHost);
                     } else if (CUSTOM_TILE_SPEC.equals(spec)) {
-                        return mCustomTile;
+                        QSTile tile = mCustomTile;
+                        QSTile.State s = mock(QSTile.State.class);
+                        s.spec = spec;
+                        when(mCustomTile.getState()).thenReturn(s);
+                        return tile;
                     } else if ("internet".equals(spec)
                             || "wifi".equals(spec)
                             || "cell".equals(spec)) {
@@ -647,7 +649,7 @@
     @Test
     public void testSetTileRemoved_removedBySystem() {
         int user = mUserTracker.getUserId();
-        saveSetting("spec1" + CUSTOM_TILE_SPEC);
+        saveSetting("spec1," + CUSTOM_TILE_SPEC);
 
         // This will be done by TileServiceManager
         mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
@@ -658,6 +660,27 @@
                 .getBoolean(CUSTOM_TILE.flattenToString(), false));
     }
 
+    @Test
+    public void testProtoDump_noTiles() {
+        SystemUIProtoDump proto = new SystemUIProtoDump();
+        mQSTileHost.dumpProto(proto, new String[0]);
+
+        assertEquals(0, proto.tiles.length);
+    }
+
+    @Test
+    public void testTilesInOrder() {
+        saveSetting("spec1," + CUSTOM_TILE_SPEC);
+
+        SystemUIProtoDump proto = new SystemUIProtoDump();
+        mQSTileHost.dumpProto(proto, new String[0]);
+
+        assertEquals(2, proto.tiles.length);
+        assertEquals("spec1", proto.tiles[0].getSpec());
+        assertEquals(CUSTOM_TILE.getPackageName(), proto.tiles[1].getComponentName().packageName);
+        assertEquals(CUSTOM_TILE.getClassName(), proto.tiles[1].getComponentName().className);
+    }
+
     private SharedPreferences getSharedPreferenecesForUser(int user) {
         return mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user);
     }
@@ -707,12 +730,9 @@
 
         @Override
         public State newTileState() {
-            return mMockState;
-        }
-
-        @Override
-        public State getState() {
-            return mMockState;
+            State s = mock(QSTile.State.class);
+            when(s.toString()).thenReturn(MOCK_STATE_STRING);
+            return s;
         }
 
         @Override
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 6af8e49..f53e997 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -23,8 +23,8 @@
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.MediaHost
-import com.android.systemui.media.MediaHostState
+import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.MediaHostState
 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/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt
new file mode 100644
index 0000000..629c663
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt
@@ -0,0 +1,104 @@
+package com.android.systemui.qs
+
+import android.content.ComponentName
+import android.service.quicksettings.Tile
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.external.CustomTile
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class TileStateToProtoTest : SysuiTestCase() {
+
+    companion object {
+        private const val TEST_LABEL = "label"
+        private const val TEST_SUBTITLE = "subtitle"
+        private const val TEST_SPEC = "spec"
+        private val TEST_COMPONENT = ComponentName("test_pkg", "test_cls")
+    }
+
+    @Test
+    fun platformTile_INACTIVE() {
+        val state =
+            QSTile.State().apply {
+                spec = TEST_SPEC
+                label = TEST_LABEL
+                secondaryLabel = TEST_SUBTITLE
+                state = Tile.STATE_INACTIVE
+            }
+        val proto = state.toProto()
+
+        assertThat(proto).isNotNull()
+        assertThat(proto?.hasSpec()).isTrue()
+        assertThat(proto?.spec).isEqualTo(TEST_SPEC)
+        assertThat(proto?.hasComponentName()).isFalse()
+        assertThat(proto?.label).isEqualTo(TEST_LABEL)
+        assertThat(proto?.secondaryLabel).isEqualTo(TEST_SUBTITLE)
+        assertThat(proto?.state).isEqualTo(Tile.STATE_INACTIVE)
+        assertThat(proto?.hasBooleanState()).isFalse()
+    }
+
+    @Test
+    fun componentTile_UNAVAILABLE() {
+        val state =
+            QSTile.State().apply {
+                spec = CustomTile.toSpec(TEST_COMPONENT)
+                label = TEST_LABEL
+                secondaryLabel = TEST_SUBTITLE
+                state = Tile.STATE_UNAVAILABLE
+            }
+        val proto = state.toProto()
+
+        assertThat(proto).isNotNull()
+        assertThat(proto?.hasSpec()).isFalse()
+        assertThat(proto?.hasComponentName()).isTrue()
+        val componentName = proto?.componentName
+        assertThat(componentName?.packageName).isEqualTo(TEST_COMPONENT.packageName)
+        assertThat(componentName?.className).isEqualTo(TEST_COMPONENT.className)
+        assertThat(proto?.label).isEqualTo(TEST_LABEL)
+        assertThat(proto?.secondaryLabel).isEqualTo(TEST_SUBTITLE)
+        assertThat(proto?.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+        assertThat(proto?.hasBooleanState()).isFalse()
+    }
+
+    @Test
+    fun booleanState_ACTIVE() {
+        val state =
+            QSTile.BooleanState().apply {
+                spec = TEST_SPEC
+                label = TEST_LABEL
+                secondaryLabel = TEST_SUBTITLE
+                state = Tile.STATE_ACTIVE
+                value = true
+            }
+        val proto = state.toProto()
+
+        assertThat(proto).isNotNull()
+        assertThat(proto?.hasSpec()).isTrue()
+        assertThat(proto?.spec).isEqualTo(TEST_SPEC)
+        assertThat(proto?.hasComponentName()).isFalse()
+        assertThat(proto?.label).isEqualTo(TEST_LABEL)
+        assertThat(proto?.secondaryLabel).isEqualTo(TEST_SUBTITLE)
+        assertThat(proto?.state).isEqualTo(Tile.STATE_ACTIVE)
+        assertThat(proto?.hasBooleanState()).isTrue()
+        assertThat(proto?.booleanState).isTrue()
+    }
+
+    @Test
+    fun noSpec_returnsNull() {
+        val state =
+            QSTile.State().apply {
+                label = TEST_LABEL
+                secondaryLabel = TEST_SUBTITLE
+                state = Tile.STATE_ACTIVE
+            }
+        val proto = state.toProto()
+
+        assertThat(proto).isNull()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
index 3c25807..2c2ddbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
@@ -23,13 +23,13 @@
 import android.provider.Settings
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
-import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.nano.MetricsProto
 import com.android.internal.logging.testing.FakeMetricsLogger
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.globalactions.GlobalActionsDialogLite
@@ -70,13 +70,13 @@
         val underTest = utils.footerActionsInteractor(qsSecurityFooterUtils = qsSecurityFooterUtils)
 
         val quickSettingsContext = mock<Context>()
-        underTest.showDeviceMonitoringDialog(quickSettingsContext)
+
+        underTest.showDeviceMonitoringDialog(quickSettingsContext, null)
         verify(qsSecurityFooterUtils).showDeviceMonitoringDialog(quickSettingsContext, null)
 
-        val view = mock<View>()
-        whenever(view.context).thenReturn(quickSettingsContext)
-        underTest.showDeviceMonitoringDialog(view)
-        verify(qsSecurityFooterUtils).showDeviceMonitoringDialog(quickSettingsContext, null)
+        val expandable = mock<Expandable>()
+        underTest.showDeviceMonitoringDialog(quickSettingsContext, expandable)
+        verify(qsSecurityFooterUtils).showDeviceMonitoringDialog(quickSettingsContext, expandable)
     }
 
     @Test
@@ -85,8 +85,8 @@
         val underTest = utils.footerActionsInteractor(uiEventLogger = uiEventLogger)
 
         val globalActionsDialogLite = mock<GlobalActionsDialogLite>()
-        val view = mock<View>()
-        underTest.showPowerMenuDialog(globalActionsDialogLite, view)
+        val expandable = mock<Expandable>()
+        underTest.showPowerMenuDialog(globalActionsDialogLite, expandable)
 
         // Event is logged.
         val logs = uiEventLogger.logs
@@ -99,7 +99,7 @@
             .showOrHideDialog(
                 /* keyguardShowing= */ false,
                 /* isDeviceProvisioned= */ true,
-                view,
+                expandable,
             )
     }
 
@@ -167,11 +167,11 @@
                 userSwitchDialogController = userSwitchDialogController,
             )
 
-        val view = mock<View>()
-        underTest.showUserSwitcher(view)
+        val expandable = mock<Expandable>()
+        underTest.showUserSwitcher(context, expandable)
 
         // Dialog is shown.
-        verify(userSwitchDialogController).showDialog(view)
+        verify(userSwitchDialogController).showDialog(context, expandable)
     }
 
     @Test
@@ -184,12 +184,9 @@
                 activityStarter = activityStarter,
             )
 
-        // The clicked view. The context is necessary because it's used to build the intent, that
-        // we check below.
-        val view = mock<View>()
-        whenever(view.context).thenReturn(context)
-
-        underTest.showUserSwitcher(view)
+        // The clicked expandable.
+        val expandable = mock<Expandable>()
+        underTest.showUserSwitcher(context, expandable)
 
         // Dialog is shown.
         val intentCaptor = argumentCaptor<Intent>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
index da52a9b..bc27bbc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.statusbar.policy.UserSwitcherController
 import com.android.systemui.user.data.source.UserRecord
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -139,6 +140,11 @@
         clickableTest(false, false, mUserDetailItemView, true)
     }
 
+    @Test
+    fun testManageUsersIsNotAvailable() {
+        assertNull(adapter.users.find { it.isManageUsers })
+    }
+
     private fun createUserRecord(current: Boolean, guest: Boolean) =
         UserRecord(
             UserInfo(0 /* id */, "name", 0 /* flags */),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
index 9d908fd..0a34810 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -20,12 +20,12 @@
 import android.content.Intent
 import android.provider.Settings
 import android.testing.AndroidTestingRunner
-import android.view.View
 import android.widget.Button
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.animation.Expandable
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.PseudoGridView
@@ -35,6 +35,7 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -63,7 +64,7 @@
     @Mock
     private lateinit var userDetailViewAdapter: UserDetailView.Adapter
     @Mock
-    private lateinit var launchView: View
+    private lateinit var launchExpandable: Expandable
     @Mock
     private lateinit var neutralButton: Button
     @Mock
@@ -79,7 +80,6 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        `when`(launchView.context).thenReturn(mContext)
         `when`(dialog.context).thenReturn(mContext)
 
         controller = UserSwitchDialogController(
@@ -94,32 +94,34 @@
 
     @Test
     fun showDialog_callsDialogShow() {
-        controller.showDialog(launchView)
-        verify(dialogLaunchAnimator).showFromView(eq(dialog), eq(launchView), any(), anyBoolean())
+        val launchController = mock<DialogLaunchAnimator.Controller>()
+        `when`(launchExpandable.dialogLaunchController(any())).thenReturn(launchController)
+        controller.showDialog(context, launchExpandable)
+        verify(dialogLaunchAnimator).show(eq(dialog), eq(launchController), anyBoolean())
         verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN)
     }
 
     @Test
     fun dialog_showForAllUsers() {
-        controller.showDialog(launchView)
+        controller.showDialog(context, launchExpandable)
         verify(dialog).setShowForAllUsers(true)
     }
 
     @Test
     fun dialog_cancelOnTouchOutside() {
-        controller.showDialog(launchView)
+        controller.showDialog(context, launchExpandable)
         verify(dialog).setCanceledOnTouchOutside(true)
     }
 
     @Test
     fun adapterAndGridLinked() {
-        controller.showDialog(launchView)
+        controller.showDialog(context, launchExpandable)
         verify(userDetailViewAdapter).linkToViewGroup(any<PseudoGridView>())
     }
 
     @Test
     fun doneButtonLogsCorrectly() {
-        controller.showDialog(launchView)
+        controller.showDialog(context, launchExpandable)
 
         verify(dialog).setPositiveButton(anyInt(), capture(clickCaptor))
 
@@ -132,7 +134,7 @@
     fun clickSettingsButton_noFalsing_opensSettings() {
         `when`(falsingManager.isFalseTap(anyInt())).thenReturn(false)
 
-        controller.showDialog(launchView)
+        controller.showDialog(context, launchExpandable)
 
         verify(dialog)
             .setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */)
@@ -153,7 +155,7 @@
     fun clickSettingsButton_Falsing_notOpensSettings() {
         `when`(falsingManager.isFalseTap(anyInt())).thenReturn(true)
 
-        controller.showDialog(launchView)
+        controller.showDialog(context, launchExpandable)
 
         verify(dialog)
             .setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
new file mode 100644
index 0000000..b6a595b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.screenshot
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+class ActionIntentCreatorTest : SysuiTestCase() {
+
+    @Test
+    fun testCreateShareIntent() {
+        val uri = Uri.parse("content://fake")
+        val subject = "Example subject"
+
+        val output = ActionIntentCreator.createShareIntent(uri, subject)
+
+        assertThat(output.action).isEqualTo(Intent.ACTION_CHOOSER)
+        assertFlagsSet(
+            Intent.FLAG_ACTIVITY_NEW_TASK or
+                Intent.FLAG_ACTIVITY_CLEAR_TASK or
+                Intent.FLAG_GRANT_READ_URI_PERMISSION,
+            output.flags
+        )
+
+        val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
+        assertThat(wrappedIntent?.action).isEqualTo(Intent.ACTION_SEND)
+        assertThat(wrappedIntent?.data).isEqualTo(uri)
+        assertThat(wrappedIntent?.type).isEqualTo("image/png")
+        assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_SUBJECT)).isEqualTo(subject)
+        assertThat(wrappedIntent?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java))
+            .isEqualTo(uri)
+    }
+
+    @Test
+    fun testCreateShareIntent_noSubject() {
+        val uri = Uri.parse("content://fake")
+        val output = ActionIntentCreator.createShareIntent(uri, null)
+        val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
+        assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_SUBJECT)).isNull()
+    }
+
+    @Test
+    fun testCreateEditIntent() {
+        val uri = Uri.parse("content://fake")
+        val context = mock<Context>()
+
+        val output = ActionIntentCreator.createEditIntent(uri, context)
+
+        assertThat(output.action).isEqualTo(Intent.ACTION_EDIT)
+        assertThat(output.data).isEqualTo(uri)
+        assertThat(output.type).isEqualTo("image/png")
+        assertThat(output.component).isNull()
+        val expectedFlags =
+            Intent.FLAG_GRANT_READ_URI_PERMISSION or
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
+                Intent.FLAG_ACTIVITY_NEW_TASK or
+                Intent.FLAG_ACTIVITY_CLEAR_TASK
+        assertFlagsSet(expectedFlags, output.flags)
+    }
+
+    @Test
+    fun testCreateEditIntent_withEditor() {
+        val uri = Uri.parse("content://fake")
+        val context = mock<Context>()
+        var component = ComponentName("com.android.foo", "com.android.foo.Something")
+
+        whenever(context.getString(eq(R.string.config_screenshotEditor)))
+            .thenReturn(component.flattenToString())
+
+        val output = ActionIntentCreator.createEditIntent(uri, context)
+
+        assertThat(output.component).isEqualTo(component)
+    }
+
+    private fun assertFlagsSet(expected: Int, observed: Int) {
+        assertThat(observed and expected).isEqualTo(expected)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
new file mode 100644
index 0000000..1130bda
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.settings.brightness
+
+import android.content.Intent
+import android.graphics.Rect
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.intercepting.SingleActivityFactory
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class BrightnessDialogTest : SysuiTestCase() {
+
+    @Mock private lateinit var brightnessSliderControllerFactory: BrightnessSliderController.Factory
+    @Mock private lateinit var backgroundHandler: Handler
+    @Mock private lateinit var brightnessSliderController: BrightnessSliderController
+
+    @Rule
+    @JvmField
+    var activityRule =
+        ActivityTestRule(
+            object : SingleActivityFactory<TestDialog>(TestDialog::class.java) {
+                override fun create(intent: Intent?): TestDialog {
+                    return TestDialog(
+                        fakeBroadcastDispatcher,
+                        brightnessSliderControllerFactory,
+                        backgroundHandler
+                    )
+                }
+            },
+            false,
+            false
+        )
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        `when`(brightnessSliderControllerFactory.create(any(), any()))
+            .thenReturn(brightnessSliderController)
+        `when`(brightnessSliderController.rootView).thenReturn(View(context))
+
+        activityRule.launchActivity(null)
+    }
+
+    @After
+    fun tearDown() {
+        activityRule.finishActivity()
+    }
+
+    @Test
+    fun testGestureExclusion() {
+        val frame = activityRule.activity.requireViewById<View>(R.id.brightness_mirror_container)
+
+        val lp = frame.layoutParams as ViewGroup.MarginLayoutParams
+        val horizontalMargin =
+            activityRule.activity.resources.getDimensionPixelSize(
+                R.dimen.notification_side_paddings
+            )
+        assertThat(lp.leftMargin).isEqualTo(horizontalMargin)
+        assertThat(lp.rightMargin).isEqualTo(horizontalMargin)
+
+        assertThat(frame.systemGestureExclusionRects.size).isEqualTo(1)
+        val exclusion = frame.systemGestureExclusionRects[0]
+        assertThat(exclusion)
+            .isEqualTo(Rect(-horizontalMargin, 0, frame.width + horizontalMargin, frame.height))
+    }
+
+    class TestDialog(
+        broadcastDispatcher: BroadcastDispatcher,
+        brightnessSliderControllerFactory: BrightnessSliderController.Factory,
+        backgroundHandler: Handler
+    ) : BrightnessDialog(broadcastDispatcher, brightnessSliderControllerFactory, backgroundHandler)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index c76d9e7..14a3bc1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -645,6 +645,65 @@
         verify(animator).start()
     }
 
+    @Test
+    fun privacyChipParentVisibleFromStart() {
+        verify(privacyIconsController).onParentVisible()
+    }
+
+    @Test
+    fun privacyChipParentVisibleAlways() {
+        controller.largeScreenActive = true
+        controller.largeScreenActive = false
+        controller.largeScreenActive = true
+
+        verify(privacyIconsController, never()).onParentInvisible()
+    }
+
+    @Test
+    fun clockPivotYInCenter() {
+        val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
+        verify(clock).addOnLayoutChangeListener(capture(captor))
+        var height = 100
+        val width = 50
+
+        clock.executeLayoutChange(0, 0, width, height, captor.value)
+        verify(clock).pivotY = height.toFloat() / 2
+
+        height = 150
+        clock.executeLayoutChange(0, 0, width, height, captor.value)
+        verify(clock).pivotY = height.toFloat() / 2
+    }
+
+    private fun View.executeLayoutChange(
+            left: Int,
+            top: Int,
+            right: Int,
+            bottom: Int,
+            listener: View.OnLayoutChangeListener
+    ) {
+        val oldLeft = this.left
+        val oldTop = this.top
+        val oldRight = this.right
+        val oldBottom = this.bottom
+        whenever(this.left).thenReturn(left)
+        whenever(this.top).thenReturn(top)
+        whenever(this.right).thenReturn(right)
+        whenever(this.bottom).thenReturn(bottom)
+        whenever(this.height).thenReturn(bottom - top)
+        whenever(this.width).thenReturn(right - left)
+        listener.onLayoutChange(
+                this,
+                oldLeft,
+                oldTop,
+                oldRight,
+                oldBottom,
+                left,
+                top,
+                right,
+                bottom
+        )
+    }
+
     private fun createWindowInsets(
         topCutout: Rect? = Rect()
     ): WindowInsets {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 37be343..02f28a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -18,6 +18,7 @@
 
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
+import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
@@ -33,11 +34,13 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -76,6 +79,7 @@
 import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.LatencyTracker;
+import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.keyguard.KeyguardClockSwitch;
 import com.android.keyguard.KeyguardClockSwitchController;
 import com.android.keyguard.KeyguardStatusView;
@@ -93,7 +97,6 @@
 import com.android.systemui.camera.CameraGestureHelper;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.controls.dagger.ControlsComponent;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
@@ -102,14 +105,15 @@
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
-import com.android.systemui.media.KeyguardMediaController;
-import com.android.systemui.media.MediaDataManager;
-import com.android.systemui.media.MediaHierarchyManager;
+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.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.shade.transition.ShadeTransitionController;
@@ -165,7 +169,6 @@
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.util.time.SystemClock;
-import com.android.systemui.wallet.controller.QuickAccessWalletController;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 
 import org.junit.After;
@@ -173,6 +176,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.stubbing.Answer;
@@ -251,17 +256,15 @@
     @Mock private KeyguardMediaController mKeyguardMediaController;
     @Mock private PrivacyDotViewController mPrivacyDotViewController;
     @Mock private NavigationModeController mNavigationModeController;
+    @Mock private NavigationBarController mNavigationBarController;
     @Mock private LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
     @Mock private ContentResolver mContentResolver;
     @Mock private TapAgainViewController mTapAgainViewController;
     @Mock private KeyguardIndicationController mKeyguardIndicationController;
     @Mock private FragmentService mFragmentService;
     @Mock private FragmentHostManager mFragmentHostManager;
-    @Mock private QuickAccessWalletController mQuickAccessWalletController;
-    @Mock private QRCodeScannerController mQrCodeScannerController;
     @Mock private NotificationRemoteInputManager mNotificationRemoteInputManager;
     @Mock private RecordingController mRecordingController;
-    @Mock private ControlsComponent mControlsComponent;
     @Mock private LockscreenGestureLogger mLockscreenGestureLogger;
     @Mock private DumpManager mDumpManager;
     @Mock private InteractionJankMonitor mInteractionJankMonitor;
@@ -282,6 +285,10 @@
     @Mock private ViewTreeObserver mViewTreeObserver;
     @Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
     @Mock private KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
+    @Mock private MotionEvent mDownMotionEvent;
+    @Captor
+    private ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener>
+            mEmptySpaceClickListenerCaptor;
 
     private NotificationPanelViewController.TouchHandler mTouchHandler;
     private ConfigurationController mConfigurationController;
@@ -373,6 +380,7 @@
 
         NotificationWakeUpCoordinator coordinator =
                 new NotificationWakeUpCoordinator(
+                        mDumpManager,
                         mock(HeadsUpManagerPhone.class),
                         new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager,
                                 mInteractionJankMonitor),
@@ -388,6 +396,7 @@
                 mConfigurationController,
                 mStatusBarStateController,
                 mFalsingManager,
+                mShadeExpansionStateManager,
                 mLockscreenShadeTransitionController,
                 new FalsingCollectorFake(),
                 mDumpManager);
@@ -425,6 +434,8 @@
         when(mView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
         when(mView.getParent()).thenReturn(mViewParent);
         when(mQs.getHeader()).thenReturn(mQsHeader);
+        when(mDownMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN);
+        when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
 
         mMainHandler = new Handler(Looper.getMainLooper());
         NotificationPanelViewController.PanelEventsEmitter panelEventsEmitter =
@@ -468,6 +479,7 @@
                 mPrivacyDotViewController,
                 mTapAgainViewController,
                 mNavigationModeController,
+                mNavigationBarController,
                 mFragmentService,
                 mContentResolver,
                 mRecordingController,
@@ -512,6 +524,8 @@
                 .addCallback(mNotificationPanelViewController.mStatusBarStateListener);
         mNotificationPanelViewController
                 .setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class));
+        verify(mNotificationStackScrollLayoutController)
+                .setOnEmptySpaceClickListener(mEmptySpaceClickListenerCaptor.capture());
     }
 
     @After
@@ -716,6 +730,72 @@
     }
 
     @Test
+    public void test_pulsing_onTouchEvent_noTracking() {
+        // GIVEN device is pulsing
+        mNotificationPanelViewController.setPulsing(true);
+
+        // WHEN touch DOWN & MOVE events received
+        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+                0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
+                0 /* metaState */));
+        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+                0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */,
+                0 /* metaState */));
+
+        // THEN touch is NOT tracked (since the device is pulsing)
+        assertThat(mNotificationPanelViewController.isTracking()).isFalse();
+    }
+
+    @Test
+    public void test_onTouchEvent_startTracking() {
+        // GIVEN device is NOT pulsing
+        mNotificationPanelViewController.setPulsing(false);
+
+        // WHEN touch DOWN & MOVE events received
+        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+                0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
+                0 /* metaState */));
+        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+                0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */,
+                0 /* metaState */));
+
+        // THEN touch is tracked
+        assertThat(mNotificationPanelViewController.isTracking()).isTrue();
+    }
+
+    @Test
+    public void testOnTouchEvent_expansionResumesAfterBriefTouch() {
+        // Start shade collapse with swipe up
+        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+                0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
+                0 /* metaState */));
+        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+                0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 300f /* y */,
+                0 /* metaState */));
+        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+                0L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */,
+                0 /* metaState */));
+
+        assertThat(mNotificationPanelViewController.getClosing()).isTrue();
+        assertThat(mNotificationPanelViewController.getIsFlinging()).isTrue();
+
+        // simulate touch that does not exceed touch slop
+        onTouchEvent(MotionEvent.obtain(2L /* downTime */,
+                2L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 300f /* y */,
+                0 /* metaState */));
+
+        mNotificationPanelViewController.setTouchSlopExceeded(false);
+
+        onTouchEvent(MotionEvent.obtain(2L /* downTime */,
+                2L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */,
+                0 /* metaState */));
+
+        // fling should still be called after a touch that does not exceed touch slop
+        assertThat(mNotificationPanelViewController.getClosing()).isTrue();
+        assertThat(mNotificationPanelViewController.getIsFlinging()).isTrue();
+    }
+
+    @Test
     public void handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
         when(mCommandQueue.panelsEnabled()).thenReturn(false);
 
@@ -1506,6 +1586,103 @@
         );
     }
 
+    @Test
+    public void onEmptySpaceClicked_notDozingAndOnKeyguard_requestsFaceAuth() {
+        StatusBarStateController.StateListener statusBarStateListener =
+                mNotificationPanelViewController.mStatusBarStateListener;
+        statusBarStateListener.onStateChanged(KEYGUARD);
+        mNotificationPanelViewController.setDozing(false, false);
+
+        // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
+        mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
+        mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
+
+        verify(mUpdateMonitor).requestFaceAuth(true,
+                FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
+    }
+
+    @Test
+    public void onEmptySpaceClicked_notDozingAndFaceDetectionIsNotRunning_startsUnlockAnimation() {
+        StatusBarStateController.StateListener statusBarStateListener =
+                mNotificationPanelViewController.mStatusBarStateListener;
+        statusBarStateListener.onStateChanged(KEYGUARD);
+        mNotificationPanelViewController.setDozing(false, false);
+        when(mUpdateMonitor.requestFaceAuth(true, NOTIFICATION_PANEL_CLICKED)).thenReturn(false);
+
+        // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
+        mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
+        mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
+
+        verify(mNotificationStackScrollLayoutController).setUnlockHintRunning(true);
+    }
+
+    @Test
+    public void onEmptySpaceClicked_notDozingAndFaceDetectionIsRunning_doesNotStartUnlockHint() {
+        StatusBarStateController.StateListener statusBarStateListener =
+                mNotificationPanelViewController.mStatusBarStateListener;
+        statusBarStateListener.onStateChanged(KEYGUARD);
+        mNotificationPanelViewController.setDozing(false, false);
+        when(mUpdateMonitor.requestFaceAuth(true, NOTIFICATION_PANEL_CLICKED)).thenReturn(true);
+
+        // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
+        mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
+        mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
+
+        verify(mNotificationStackScrollLayoutController, never()).setUnlockHintRunning(true);
+    }
+
+    @Test
+    public void onEmptySpaceClicked_whenDozingAndOnKeyguard_doesNotRequestFaceAuth() {
+        StatusBarStateController.StateListener statusBarStateListener =
+                mNotificationPanelViewController.mStatusBarStateListener;
+        statusBarStateListener.onStateChanged(KEYGUARD);
+        mNotificationPanelViewController.setDozing(true, false);
+
+        // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
+        mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
+        mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
+
+        verify(mUpdateMonitor, never()).requestFaceAuth(anyBoolean(), anyString());
+    }
+
+    @Test
+    public void onEmptySpaceClicked_whenStatusBarShadeLocked_doesNotRequestFaceAuth() {
+        StatusBarStateController.StateListener statusBarStateListener =
+                mNotificationPanelViewController.mStatusBarStateListener;
+        statusBarStateListener.onStateChanged(SHADE_LOCKED);
+
+        mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
+
+        verify(mUpdateMonitor, never()).requestFaceAuth(anyBoolean(), anyString());
+
+    }
+
+    /**
+     * When shade is flinging to close and this fling is not intercepted,
+     * {@link AmbientState#setIsClosing(boolean)} should be called before
+     * {@link NotificationStackScrollLayoutController#onExpansionStopped()}
+     * to ensure scrollY can be correctly set to be 0
+     */
+    @Test
+    public void onShadeFlingClosingEnd_mAmbientStateSetClose_thenOnExpansionStopped() {
+        // Given: Shade is expanded
+        mNotificationPanelViewController.notifyExpandingFinished();
+        mNotificationPanelViewController.setIsClosing(false);
+
+        // When: Shade flings to close not canceled
+        mNotificationPanelViewController.notifyExpandingStarted();
+        mNotificationPanelViewController.setIsClosing(true);
+        mNotificationPanelViewController.onFlingEnd(false);
+
+        // Then: AmbientState's mIsClosing should be set to false
+        // before mNotificationStackScrollLayoutController.onExpansionStopped() is called
+        // to ensure NotificationStackScrollLayout.resetScrollPosition() -> resetScrollPosition
+        // -> setOwnScrollY(0) can set scrollY to 0 when shade is closed
+        InOrder inOrder = inOrder(mAmbientState, mNotificationStackScrollLayoutController);
+        inOrder.verify(mAmbientState).setIsClosing(false);
+        inOrder.verify(mNotificationStackScrollLayoutController).onExpansionStopped();
+    }
+
     private static MotionEvent createMotionEvent(int x, int y, int action) {
         return MotionEvent.obtain(
                 /* downTime= */ 0, /* eventTime= */ 0, action, x, y, /* metaState= */ 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
index 12ef036..bdafc7d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
@@ -66,6 +66,8 @@
     @Mock
     private lateinit var largeScreenShadeHeaderController: LargeScreenShadeHeaderController
     @Mock
+    private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+    @Mock
     private lateinit var featureFlags: FeatureFlags
     @Captor
     lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
@@ -96,6 +98,7 @@
                 navigationModeController,
                 overviewProxyService,
                 largeScreenShadeHeaderController,
+                shadeExpansionStateManager,
                 featureFlags,
                 delayableExecutor
         )
@@ -380,6 +383,7 @@
                 navigationModeController,
                 overviewProxyService,
                 largeScreenShadeHeaderController,
+                shadeExpansionStateManager,
                 featureFlags,
                 delayableExecutor
         )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index ad3d3d2..95cf9d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -88,6 +88,7 @@
     @Mock private KeyguardStateController mKeyguardStateController;
     @Mock private ScreenOffAnimationController mScreenOffAnimationController;
     @Mock private AuthController mAuthController;
+    @Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
     @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
 
     private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
@@ -103,7 +104,7 @@
                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
                 mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
                 mColorExtractor, mDumpManager, mKeyguardStateController,
-                mScreenOffAnimationController, mAuthController) {
+                mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager) {
                     @Override
                     protected boolean isDebuggable() {
                         return false;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
index eb34561..cc45cf88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.TextAnimator
+import com.android.systemui.util.mockito.any
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -55,7 +56,7 @@
         clockView.animateAppearOnLockscreen()
         clockView.measure(50, 50)
 
-        verify(mockTextAnimator).glyphFilter = null
+        verify(mockTextAnimator).glyphFilter = any()
         verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, false, 350L, null, 0L, null)
         verifyNoMoreInteractions(mockTextAnimator)
     }
@@ -66,7 +67,7 @@
         clockView.measure(50, 50)
         clockView.animateAppearOnLockscreen()
 
-        verify(mockTextAnimator, times(2)).glyphFilter = null
+        verify(mockTextAnimator, times(2)).glyphFilter = any()
         verify(mockTextAnimator).setTextStyle(100, -1.0f, 200, false, 0L, null, 0L, null)
         verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, true, 350L, null, 0L, null)
         verifyNoMoreInteractions(mockTextAnimator)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
index cf5fa87..64dc956 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
@@ -16,6 +16,11 @@
 
 package com.android.systemui.shared.system;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.view.RemoteAnimationTarget.MODE_CHANGING;
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OPEN;
@@ -25,11 +30,6 @@
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_STANDARD;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CHANGING;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -40,6 +40,7 @@
 import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.window.TransitionInfo;
@@ -73,12 +74,12 @@
                 .addChange(TRANSIT_OPEN, FLAG_IS_WALLPAPER, null /* taskInfo */)
                 .addChange(TRANSIT_CHANGE, FLAG_FIRST_CUSTOM, null /* taskInfo */).build();
         // Check apps extraction
-        RemoteAnimationTargetCompat[] wrapped = RemoteAnimationTargetCompat.wrapApps(combined,
+        RemoteAnimationTarget[] wrapped = RemoteAnimationTargetCompat.wrapApps(combined,
                 mock(SurfaceControl.Transaction.class), null /* leashes */);
         assertEquals(2, wrapped.length);
         int changeLayer = -1;
         int closeLayer = -1;
-        for (RemoteAnimationTargetCompat t : wrapped) {
+        for (RemoteAnimationTarget t : wrapped) {
             if (t.mode == MODE_CHANGING) {
                 changeLayer = t.prefixOrderIndex;
             } else if (t.mode == MODE_CLOSING) {
@@ -91,14 +92,14 @@
         assertTrue(closeLayer < changeLayer);
 
         // Check wallpaper extraction
-        RemoteAnimationTargetCompat[] wallps = RemoteAnimationTargetCompat.wrapNonApps(combined,
+        RemoteAnimationTarget[] wallps = RemoteAnimationTargetCompat.wrapNonApps(combined,
                 true /* wallpapers */, mock(SurfaceControl.Transaction.class), null /* leashes */);
         assertEquals(1, wallps.length);
         assertTrue(wallps[0].prefixOrderIndex < closeLayer);
         assertEquals(MODE_OPENING, wallps[0].mode);
 
         // Check non-apps extraction
-        RemoteAnimationTargetCompat[] nonApps = RemoteAnimationTargetCompat.wrapNonApps(combined,
+        RemoteAnimationTarget[] nonApps = RemoteAnimationTargetCompat.wrapNonApps(combined,
                 false /* wallpapers */, mock(SurfaceControl.Transaction.class), null /* leashes */);
         assertEquals(1, nonApps.length);
         assertTrue(nonApps[0].prefixOrderIndex < closeLayer);
@@ -115,9 +116,9 @@
         change.setTaskInfo(createTaskInfo(1 /* taskId */, ACTIVITY_TYPE_HOME));
         change.setEndAbsBounds(endBounds);
         change.setEndRelOffset(0, 0);
-        final RemoteAnimationTargetCompat wrapped = new RemoteAnimationTargetCompat(change,
-                0 /* order */, tinfo, mock(SurfaceControl.Transaction.class));
-        assertEquals(ACTIVITY_TYPE_HOME, wrapped.activityType);
+        RemoteAnimationTarget wrapped = RemoteAnimationTargetCompat.newTarget(
+                change, 0 /* order */, tinfo, mock(SurfaceControl.Transaction.class), null);
+        assertEquals(ACTIVITY_TYPE_HOME, wrapped.windowConfiguration.getActivityType());
         assertEquals(new Rect(0, 0, 100, 140), wrapped.localBounds);
         assertEquals(endBounds, wrapped.screenSpaceBounds);
         assertTrue(wrapped.isTranslucent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt
index 5b34a95..b761647 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt
@@ -17,58 +17,58 @@
 
 @SmallTest
 class UncaughtExceptionPreHandlerTest : SysuiTestCase() {
-  private lateinit var preHandlerManager: UncaughtExceptionPreHandlerManager
+    private lateinit var preHandlerManager: UncaughtExceptionPreHandlerManager
 
-  @Mock private lateinit var mockHandler: UncaughtExceptionHandler
+    @Mock private lateinit var mockHandler: UncaughtExceptionHandler
 
-  @Mock private lateinit var mockHandler2: UncaughtExceptionHandler
+    @Mock private lateinit var mockHandler2: UncaughtExceptionHandler
 
-  @Before
-  fun setUp() {
-    MockitoAnnotations.initMocks(this)
-    Thread.setUncaughtExceptionPreHandler(null)
-    preHandlerManager = UncaughtExceptionPreHandlerManager()
-  }
-
-  @Test
-  fun registerHandler_registersOnceOnly() {
-    preHandlerManager.registerHandler(mockHandler)
-    preHandlerManager.registerHandler(mockHandler)
-    preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
-    verify(mockHandler, only()).uncaughtException(any(), any())
-  }
-
-  @Test
-  fun registerHandler_setsUncaughtExceptionPreHandler() {
-    Thread.setUncaughtExceptionPreHandler(null)
-    preHandlerManager.registerHandler(mockHandler)
-    assertThat(Thread.getUncaughtExceptionPreHandler()).isNotNull()
-  }
-
-  @Test
-  fun registerHandler_preservesOriginalHandler() {
-    Thread.setUncaughtExceptionPreHandler(mockHandler)
-    preHandlerManager.registerHandler(mockHandler2)
-    preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
-    verify(mockHandler, only()).uncaughtException(any(), any())
-  }
-
-  @Test
-  @Ignore
-  fun registerHandler_toleratesHandlersThatThrow() {
-    `when`(mockHandler2.uncaughtException(any(), any())).thenThrow(RuntimeException())
-    preHandlerManager.registerHandler(mockHandler2)
-    preHandlerManager.registerHandler(mockHandler)
-    preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
-    verify(mockHandler2, only()).uncaughtException(any(), any())
-    verify(mockHandler, only()).uncaughtException(any(), any())
-  }
-
-  @Test
-  fun registerHandler_doesNotSetUpTwice() {
-    UncaughtExceptionPreHandlerManager().registerHandler(mockHandler2)
-    assertThrows(IllegalStateException::class.java) {
-      preHandlerManager.registerHandler(mockHandler)
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        Thread.setUncaughtExceptionPreHandler(null)
+        preHandlerManager = UncaughtExceptionPreHandlerManager()
     }
-  }
+
+    @Test
+    fun registerHandler_registersOnceOnly() {
+        preHandlerManager.registerHandler(mockHandler)
+        preHandlerManager.registerHandler(mockHandler)
+        preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
+        verify(mockHandler, only()).uncaughtException(any(), any())
+    }
+
+    @Test
+    fun registerHandler_setsUncaughtExceptionPreHandler() {
+        Thread.setUncaughtExceptionPreHandler(null)
+        preHandlerManager.registerHandler(mockHandler)
+        assertThat(Thread.getUncaughtExceptionPreHandler()).isNotNull()
+    }
+
+    @Test
+    fun registerHandler_preservesOriginalHandler() {
+        Thread.setUncaughtExceptionPreHandler(mockHandler)
+        preHandlerManager.registerHandler(mockHandler2)
+        preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
+        verify(mockHandler, only()).uncaughtException(any(), any())
+    }
+
+    @Test
+    @Ignore
+    fun registerHandler_toleratesHandlersThatThrow() {
+        `when`(mockHandler2.uncaughtException(any(), any())).thenThrow(RuntimeException())
+        preHandlerManager.registerHandler(mockHandler2)
+        preHandlerManager.registerHandler(mockHandler)
+        preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
+        verify(mockHandler2, only()).uncaughtException(any(), any())
+        verify(mockHandler, only()).uncaughtException(any(), any())
+    }
+
+    @Test
+    fun registerHandler_doesNotSetUpTwice() {
+        UncaughtExceptionPreHandlerManager().registerHandler(mockHandler2)
+        assertThrows(IllegalStateException::class.java) {
+            preHandlerManager.registerHandler(mockHandler)
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
index 8cb530c..5fc0ffe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
@@ -4,7 +4,7 @@
 import android.util.DisplayMetrics
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.statusbar.phone.LSShadeTransitionLogger
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger
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 8643e86..3d11ced 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -10,7 +10,7 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.media.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.MediaHierarchyManager
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.shade.NotificationPanelViewController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
index 44cbe51..fbb8ebf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager
@@ -56,6 +57,7 @@
     private val configurationController: ConfigurationController = mock()
     private val statusBarStateController: StatusBarStateController = mock()
     private val falsingManager: FalsingManager = mock()
+    private val shadeExpansionStateManager: ShadeExpansionStateManager = mock()
     private val lockscreenShadeTransitionController: LockscreenShadeTransitionController = mock()
     private val falsingCollector: FalsingCollector = mock()
     private val dumpManager: DumpManager = mock()
@@ -65,7 +67,8 @@
     fun setUp() {
         whenever(expandableView.collapsedHeight).thenReturn(collapsedHeight)
 
-        pulseExpansionHandler = PulseExpansionHandler(
+        pulseExpansionHandler =
+            PulseExpansionHandler(
                 mContext,
                 wakeUpCoordinator,
                 bypassController,
@@ -74,10 +77,11 @@
                 configurationController,
                 statusBarStateController,
                 falsingManager,
+                shadeExpansionStateManager,
                 lockscreenShadeTransitionController,
                 falsingCollector,
                 dumpManager
-        )
+            )
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index f8a0d2f..9c65fac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -70,7 +70,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.telephony.TelephonyListenerManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index ed8a3e1..4bed4a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -38,7 +38,7 @@
 import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.settingslib.net.DataUsageController;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.CarrierConfigTracker;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index a76676e..d5f5105 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -43,7 +43,7 @@
 import com.android.settingslib.net.DataUsageController;
 import com.android.systemui.R;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.CarrierConfigTracker;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
index 4b458f5..dda7fad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
@@ -31,8 +31,8 @@
     private long mCreationTime = 0;
     @Nullable private GroupEntry mParent = GroupEntry.ROOT_ENTRY;
     private NotifSection mNotifSection;
-    private NotificationEntry mSummary = null;
-    private List<NotificationEntry> mChildren = new ArrayList<>();
+    @Nullable private NotificationEntry mSummary = null;
+    private final List<NotificationEntry> mChildren = new ArrayList<>();
 
     /** Builds a new instance of GroupEntry */
     public GroupEntry build() {
@@ -41,7 +41,9 @@
         ge.getAttachState().setSection(mNotifSection);
 
         ge.setSummary(mSummary);
-        mSummary.setParent(ge);
+        if (mSummary != null) {
+            mSummary.setParent(ge);
+        }
 
         for (NotificationEntry child : mChildren) {
             ge.addChild(child);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 851517e..3b05321 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -1498,45 +1498,8 @@
     }
 
     @Test
-    public void testMissingRankingWhenRemovalFeatureIsDisabled() {
+    public void testMissingRanking() {
         // GIVEN a pipeline with one two notifications
-        when(mNotifPipelineFlags.removeUnrankedNotifs()).thenReturn(false);
-        String key1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 1, "myTag")).key;
-        String key2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2, "myTag")).key;
-        NotificationEntry entry1 = mCollectionListener.getEntry(key1);
-        NotificationEntry entry2 = mCollectionListener.getEntry(key2);
-        clearInvocations(mCollectionListener);
-
-        // GIVEN the message for removing key1 gets does not reach NotifCollection
-        Ranking ranking1 = mNoMan.removeRankingWithoutEvent(key1);
-        // WHEN the message for removing key2 arrives
-        mNoMan.retractNotif(entry2.getSbn(), REASON_APP_CANCEL);
-
-        // THEN only entry2 gets removed
-        verify(mCollectionListener).onEntryRemoved(eq(entry2), eq(REASON_APP_CANCEL));
-        verify(mCollectionListener).onEntryCleanUp(eq(entry2));
-        verify(mCollectionListener).onRankingApplied();
-        verifyNoMoreInteractions(mCollectionListener);
-        verify(mLogger).logMissingRankings(eq(List.of(entry1)), eq(1), any());
-        verify(mLogger, never()).logRecoveredRankings(any(), anyInt());
-        clearInvocations(mCollectionListener, mLogger);
-
-        // WHEN a ranking update includes key1 again
-        mNoMan.setRanking(key1, ranking1);
-        mNoMan.issueRankingUpdate();
-
-        // VERIFY that we do nothing but log the 'recovery'
-        verify(mCollectionListener).onRankingUpdate(any());
-        verify(mCollectionListener).onRankingApplied();
-        verifyNoMoreInteractions(mCollectionListener);
-        verify(mLogger, never()).logMissingRankings(any(), anyInt(), any());
-        verify(mLogger).logRecoveredRankings(eq(List.of(key1)), eq(0));
-    }
-
-    @Test
-    public void testMissingRankingWhenRemovalFeatureIsEnabled() {
-        // GIVEN a pipeline with one two notifications
-        when(mNotifPipelineFlags.removeUnrankedNotifs()).thenReturn(true);
         String key1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 1, "myTag")).key;
         String key2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2, "myTag")).key;
         NotificationEntry entry1 = mCollectionListener.getEntry(key1);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 82e32b2..09f8a10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -34,10 +34,12 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import static java.util.Arrays.asList;
 import static java.util.Collections.singletonList;
@@ -135,6 +137,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         allowTestableLooperAsMainThread();
+        when(mNotifPipelineFlags.isStabilityIndexFixEnabled()).thenReturn(true);
 
         mListBuilder = new ShadeListBuilder(
                 mDumpManager,
@@ -1995,22 +1998,89 @@
     }
 
     @Test
-    public void testStableOrdering() {
+    public void testActiveOrdering_withLegacyStability() {
+        when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(false);
+        assertOrder("ABCDEFG", "ABCDEFG", "ABCDEFG", true); // no change
+        assertOrder("ABCDEFG", "ACDEFXBG", "ACDEFXBG", true); // X
+        assertOrder("ABCDEFG", "ACDEFBG", "ACDEFBG", true); // no change
+        assertOrder("ABCDEFG", "ACDEFBXZG", "ACDEFBXZG", true); // Z and X
+        assertOrder("ABCDEFG", "AXCDEZFBG", "AXCDEZFBG", true); // Z and X + gap
+    }
+
+    @Test
+    public void testStableOrdering_withLegacyStability() {
+        when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(false);
         mStabilityManager.setAllowEntryReordering(false);
-        assertOrder("ABCDEFG", "ACDEFXBG", "XABCDEFG"); // X
-        assertOrder("ABCDEFG", "ACDEFBG", "ABCDEFG"); // no change
-        assertOrder("ABCDEFG", "ACDEFBXZG", "XZABCDEFG"); // Z and X
-        assertOrder("ABCDEFG", "AXCDEZFBG", "XZABCDEFG"); // Z and X + gap
-        verify(mStabilityManager, times(4)).onEntryReorderSuppressed();
+        assertOrder("ABCDEFG", "ABCDEFG", "ABCDEFG", true); // no change
+        assertOrder("ABCDEFG", "ACDEFXBG", "XABCDEFG", false); // X
+        assertOrder("ABCDEFG", "ACDEFBG", "ABCDEFG", false); // no change
+        assertOrder("ABCDEFG", "ACDEFBXZG", "XZABCDEFG", false); // Z and X
+        assertOrder("ABCDEFG", "AXCDEZFBG", "XZABCDEFG", false); // Z and X + gap
+    }
+
+    @Test
+    public void testStableOrdering() {
+        when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(true);
+        mStabilityManager.setAllowEntryReordering(false);
+        // No input or output
+        assertOrder("", "", "", true);
+        // Remove everything
+        assertOrder("ABCDEFG", "", "", true);
+        // Literally no changes
+        assertOrder("ABCDEFG", "ABCDEFG", "ABCDEFG", true);
+
+        // No stable order
+        assertOrder("", "ABCDEFG", "ABCDEFG", true);
+
+        // F moved after A, and...
+        assertOrder("ABCDEFG", "AFBCDEG", "ABCDEFG", false);   // No other changes
+        assertOrder("ABCDEFG", "AXFBCDEG", "AXBCDEFG", false); // Insert X before F
+        assertOrder("ABCDEFG", "AFXBCDEG", "AXBCDEFG", false); // Insert X after F
+        assertOrder("ABCDEFG", "AFBCDEXG", "ABCDEFXG", false); // Insert X where F was
+
+        // B moved after F, and...
+        assertOrder("ABCDEFG", "ACDEFBG", "ABCDEFG", false);   // No other changes
+        assertOrder("ABCDEFG", "ACDEFXBG", "ABCDEFXG", false); // Insert X before B
+        assertOrder("ABCDEFG", "ACDEFBXG", "ABCDEFXG", false); // Insert X after B
+        assertOrder("ABCDEFG", "AXCDEFBG", "AXBCDEFG", false); // Insert X where B was
+
+        // Swap F and B, and...
+        assertOrder("ABCDEFG", "AFCDEBG", "ABCDEFG", false);   // No other changes
+        assertOrder("ABCDEFG", "AXFCDEBG", "AXBCDEFG", false); // Insert X before F
+        assertOrder("ABCDEFG", "AFXCDEBG", "AXBCDEFG", false); // Insert X after F
+        assertOrder("ABCDEFG", "AFCXDEBG", "AXBCDEFG", false); // Insert X between CD (or: ABCXDEFG)
+        assertOrder("ABCDEFG", "AFCDXEBG", "ABCDXEFG", false); // Insert X between DE (or: ABCDEFXG)
+        assertOrder("ABCDEFG", "AFCDEXBG", "ABCDEFXG", false); // Insert X before B
+        assertOrder("ABCDEFG", "AFCDEBXG", "ABCDEFXG", false); // Insert X after B
+
+        // Remove a bunch of entries at once
+        assertOrder("ABCDEFGHIJKL", "ACEGHI", "ACEGHI", true);
+
+        // Remove a bunch of entries and scramble
+        assertOrder("ABCDEFGHIJKL", "GCEHAI", "ACEGHI", false);
+
+        // Add a bunch of entries at once
+        assertOrder("ABCDEFG", "AVBWCXDYZEFG", "AVBWCXDYZEFG", true);
+
+        // Add a bunch of entries and reverse originals
+        // NOTE: Some of these don't have obviously correct answers
+        assertOrder("ABCDEFG", "GFEBCDAVWXYZ", "ABCDEFGVWXYZ", false); // appended
+        assertOrder("ABCDEFG", "VWXYZGFEBCDA", "VWXYZABCDEFG", false); // prepended
+        assertOrder("ABCDEFG", "GFEBVWXYZCDA", "ABCDEFGVWXYZ", false); // closer to back: append
+        assertOrder("ABCDEFG", "GFEVWXYZBCDA", "VWXYZABCDEFG", false); // closer to front: prepend
+        assertOrder("ABCDEFG", "GFEVWBXYZCDA", "VWABCDEFGXYZ", false); // split new entries
+
+        // Swap 2 pairs ("*BC*NO*"->"*NO*CB*"), remove EG, add UVWXYZ throughout
+        assertOrder("ABCDEFGHIJKLMNOP", "AUNOVDFHWXIJKLMYCBZP", "AUVBCDFHWXIJKLMNOYZP", false);
     }
 
     @Test
     public void testActiveOrdering() {
-        assertOrder("ABCDEFG", "ACDEFXBG", "ACDEFXBG"); // X
-        assertOrder("ABCDEFG", "ACDEFBG", "ACDEFBG"); // no change
-        assertOrder("ABCDEFG", "ACDEFBXZG", "ACDEFBXZG"); // Z and X
-        assertOrder("ABCDEFG", "AXCDEZFBG", "AXCDEZFBG"); // Z and X + gap
-        verify(mStabilityManager, never()).onEntryReorderSuppressed();
+        when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(true);
+        assertOrder("ABCDEFG", "ACDEFXBG", "ACDEFXBG", true); // X
+        assertOrder("ABCDEFG", "ACDEFBG", "ACDEFBG", true); // no change
+        assertOrder("ABCDEFG", "ACDEFBXZG", "ACDEFBXZG", true); // Z and X
+        assertOrder("ABCDEFG", "AXCDEZFBG", "AXCDEZFBG", true); // Z and X + gap
     }
 
     @Test
@@ -2062,6 +2132,52 @@
     }
 
     @Test
+    public void stableOrderingDisregardedWithSectionChange() {
+        when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(true);
+        // GIVEN the first sectioner's packages can be changed from run-to-run
+        List<String> mutableSectionerPackages = new ArrayList<>();
+        mutableSectionerPackages.add(PACKAGE_1);
+        mListBuilder.setSectioners(asList(
+                new PackageSectioner(mutableSectionerPackages, null),
+                new PackageSectioner(List.of(PACKAGE_1, PACKAGE_2, PACKAGE_3), null)));
+        mStabilityManager.setAllowEntryReordering(false);
+
+        // WHEN the list is originally built with reordering disabled (and section changes allowed)
+        addNotif(0, PACKAGE_1).setRank(4);
+        addNotif(1, PACKAGE_1).setRank(5);
+        addNotif(2, PACKAGE_2).setRank(1);
+        addNotif(3, PACKAGE_2).setRank(2);
+        addNotif(4, PACKAGE_3).setRank(3);
+        dispatchBuild();
+
+        // VERIFY the order and that entry reordering has not been suppressed
+        verifyBuiltList(
+                notif(0),
+                notif(1),
+                notif(2),
+                notif(3),
+                notif(4)
+        );
+        verify(mStabilityManager, never()).onEntryReorderSuppressed();
+
+        // WHEN the first section now claims PACKAGE_3 notifications
+        mutableSectionerPackages.add(PACKAGE_3);
+        dispatchBuild();
+
+        // VERIFY the re-sectioned notification is inserted at #1 of the first section, which
+        // is the correct position based on its rank, rather than #3 in the new section simply
+        // because it was #3 in its previous section.
+        verifyBuiltList(
+                notif(4),
+                notif(0),
+                notif(1),
+                notif(2),
+                notif(3)
+        );
+        verify(mStabilityManager, never()).onEntryReorderSuppressed();
+    }
+
+    @Test
     public void testStableChildOrdering() {
         // WHEN the list is originally built with reordering disabled
         mStabilityManager.setAllowEntryReordering(false);
@@ -2112,6 +2228,85 @@
         );
     }
 
+    @Test
+    public void groupRevertingToSummaryDoesNotRetainStablePositionWithLegacyIndexLogic() {
+        when(mNotifPipelineFlags.isStabilityIndexFixEnabled()).thenReturn(false);
+
+        // GIVEN a notification group is on screen
+        mStabilityManager.setAllowEntryReordering(false);
+
+        // WHEN the list is originally built with reordering disabled (and section changes allowed)
+        addNotif(0, PACKAGE_1).setRank(2);
+        addNotif(1, PACKAGE_1).setRank(3);
+        addGroupSummary(2, PACKAGE_1, "group").setRank(4);
+        addGroupChild(3, PACKAGE_1, "group").setRank(5);
+        addGroupChild(4, PACKAGE_1, "group").setRank(6);
+        dispatchBuild();
+
+        verifyBuiltList(
+                notif(0),
+                notif(1),
+                group(
+                        summary(2),
+                        child(3),
+                        child(4)
+                )
+        );
+
+        // WHEN the notification summary rank increases and children removed
+        setNewRank(notif(2).entry, 1);
+        mEntrySet.remove(4);
+        mEntrySet.remove(3);
+        dispatchBuild();
+
+        // VERIFY the summary (incorrectly) moves to the top of the section where it is ranked,
+        // despite visual stability being active
+        verifyBuiltList(
+                notif(2),
+                notif(0),
+                notif(1)
+        );
+    }
+
+    @Test
+    public void groupRevertingToSummaryRetainsStablePosition() {
+        when(mNotifPipelineFlags.isStabilityIndexFixEnabled()).thenReturn(true);
+
+        // GIVEN a notification group is on screen
+        mStabilityManager.setAllowEntryReordering(false);
+
+        // WHEN the list is originally built with reordering disabled (and section changes allowed)
+        addNotif(0, PACKAGE_1).setRank(2);
+        addNotif(1, PACKAGE_1).setRank(3);
+        addGroupSummary(2, PACKAGE_1, "group").setRank(4);
+        addGroupChild(3, PACKAGE_1, "group").setRank(5);
+        addGroupChild(4, PACKAGE_1, "group").setRank(6);
+        dispatchBuild();
+
+        verifyBuiltList(
+                notif(0),
+                notif(1),
+                group(
+                        summary(2),
+                        child(3),
+                        child(4)
+                )
+        );
+
+        // WHEN the notification summary rank increases and children removed
+        setNewRank(notif(2).entry, 1);
+        mEntrySet.remove(4);
+        mEntrySet.remove(3);
+        dispatchBuild();
+
+        // VERIFY the summary stays in the same location on rebuild
+        verifyBuiltList(
+                notif(0),
+                notif(1),
+                notif(2)
+        );
+    }
+
     private static void setNewRank(NotificationEntry entry, int rank) {
         entry.setRanking(new RankingBuilder(entry.getRanking()).setRank(rank).build());
     }
@@ -2255,26 +2450,35 @@
         return addGroupChildWithTag(index, packageId, groupId, null);
     }
 
-    private void assertOrder(String visible, String active, String expected) {
+    private void assertOrder(String visible, String active, String expected,
+            boolean isOrderedCorrectly) {
         StringBuilder differenceSb = new StringBuilder();
+        NotifSection section = new NotifSection(mock(NotifSectioner.class), 0);
         for (char c : active.toCharArray()) {
             if (visible.indexOf(c) < 0) differenceSb.append(c);
         }
         String difference = differenceSb.toString();
 
+        int globalIndex = 0;
         for (int i = 0; i < visible.length(); i++) {
-            addNotif(i, String.valueOf(visible.charAt(i)))
-                    .setRank(active.indexOf(visible.charAt(i)))
+            final char c = visible.charAt(i);
+            // Skip notifications which aren't active anymore
+            if (!active.contains(String.valueOf(c))) continue;
+            addNotif(globalIndex++, String.valueOf(c))
+                    .setRank(active.indexOf(c))
+                    .setSection(section)
                     .setStableIndex(i);
-
         }
 
-        for (int i = 0; i < difference.length(); i++) {
-            addNotif(i + visible.length(), String.valueOf(difference.charAt(i)))
-                    .setRank(active.indexOf(difference.charAt(i)))
+        for (char c : difference.toCharArray()) {
+            addNotif(globalIndex++, String.valueOf(c))
+                    .setRank(active.indexOf(c))
+                    .setSection(section)
                     .setStableIndex(-1);
         }
 
+        clearInvocations(mStabilityManager);
+
         dispatchBuild();
         StringBuilder resultSb = new StringBuilder();
         for (int i = 0; i < expected.length(); i++) {
@@ -2284,6 +2488,9 @@
         assertEquals("visible [" + visible + "] active [" + active + "]",
                 expected, resultSb.toString());
         mEntrySet.clear();
+
+        verify(mStabilityManager, isOrderedCorrectly ? never() : times(1))
+                .onEntryReorderSuppressed();
     }
 
     private int nextId(String packageName) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 340bc96..3ff7639 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -674,7 +674,9 @@
     @Test
     fun testOnRankingApplied_newEntryShouldAlert() {
         // GIVEN that mEntry has never interrupted in the past, and now should
+        // and is new enough to do so
         assertFalse(mEntry.hasInterrupted())
+        mCoordinator.setUpdateTime(mEntry, mSystemClock.currentTimeMillis())
         setShouldHeadsUp(mEntry)
         whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
 
@@ -690,8 +692,9 @@
 
     @Test
     fun testOnRankingApplied_alreadyAlertedEntryShouldNotAlertAgain() {
-        // GIVEN that mEntry has alerted in the past
+        // GIVEN that mEntry has alerted in the past, even if it's new
         mEntry.setInterruption()
+        mCoordinator.setUpdateTime(mEntry, mSystemClock.currentTimeMillis())
         setShouldHeadsUp(mEntry)
         whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
 
@@ -725,6 +728,27 @@
         verify(mHeadsUpManager).showNotification(mEntry)
     }
 
+    @Test
+    fun testOnRankingApplied_entryUpdatedButTooOld() {
+        // GIVEN that mEntry is added in a state where it should not HUN
+        setShouldHeadsUp(mEntry, false)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        // and it was actually added 10s ago
+        mCoordinator.setUpdateTime(mEntry, mSystemClock.currentTimeMillis() - 10000)
+
+        // WHEN it is updated to HUN and then a ranking update occurs
+        setShouldHeadsUp(mEntry)
+        whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+        mCollectionListener.onRankingApplied()
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+        // THEN the notification is never bound or shown
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+        verify(mHeadsUpManager, never()).showNotification(any())
+    }
+
     private fun setShouldHeadsUp(entry: NotificationEntry, should: Boolean = true) {
         whenever(mNotificationInterruptStateProvider.shouldHeadsUp(entry)).thenReturn(should)
         whenever(mNotificationInterruptStateProvider.checkHeadsUp(eq(entry), any()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
index e1e5051..590c902 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
@@ -35,7 +35,7 @@
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.MediaFeatureFlag;
+import com.android.systemui.media.controls.util.MediaFeatureFlag;
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index f4adf69..b6b0b77 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -181,7 +181,7 @@
     @Test
     public void testInflatesNewNotification() {
         // WHEN there is a new notification
-        mCollectionListener.onEntryAdded(mEntry);
+        mCollectionListener.onEntryInit(mEntry);
         mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
 
         // THEN we inflate it
@@ -194,7 +194,7 @@
     @Test
     public void testRebindsInflatedNotificationsOnUpdate() {
         // GIVEN an inflated notification
-        mCollectionListener.onEntryAdded(mEntry);
+        mCollectionListener.onEntryInit(mEntry);
         mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
         verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
         mNotifInflater.invokeInflateCallbackForEntry(mEntry);
@@ -213,7 +213,7 @@
     @Test
     public void testEntrySmartReplyAdditionWillRebindViews() {
         // GIVEN an inflated notification
-        mCollectionListener.onEntryAdded(mEntry);
+        mCollectionListener.onEntryInit(mEntry);
         mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
         verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
         mNotifInflater.invokeInflateCallbackForEntry(mEntry);
@@ -232,7 +232,7 @@
     @Test
     public void testEntryChangedToMinimizedSectionWillRebindViews() {
         // GIVEN an inflated notification
-        mCollectionListener.onEntryAdded(mEntry);
+        mCollectionListener.onEntryInit(mEntry);
         mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
         verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any());
         assertFalse(mParamsCaptor.getValue().isLowPriority());
@@ -254,7 +254,7 @@
     public void testMinimizedEntryMovedIntoGroupWillRebindViews() {
         // GIVEN an inflated, minimized notification
         setSectionIsLowPriority(true);
-        mCollectionListener.onEntryAdded(mEntry);
+        mCollectionListener.onEntryInit(mEntry);
         mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
         verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any());
         assertTrue(mParamsCaptor.getValue().isLowPriority());
@@ -275,7 +275,7 @@
     @Test
     public void testEntryRankChangeWillNotRebindViews() {
         // GIVEN an inflated notification
-        mCollectionListener.onEntryAdded(mEntry);
+        mCollectionListener.onEntryInit(mEntry);
         mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
         verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
         mNotifInflater.invokeInflateCallbackForEntry(mEntry);
@@ -294,7 +294,7 @@
     @Test
     public void testDoesntFilterInflatedNotifs() {
         // GIVEN an inflated notification
-        mCollectionListener.onEntryAdded(mEntry);
+        mCollectionListener.onEntryInit(mEntry);
         mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
         verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
         mNotifInflater.invokeInflateCallbackForEntry(mEntry);
@@ -330,9 +330,9 @@
             mCollectionListener.onEntryInit(entry);
         }
 
-        mCollectionListener.onEntryAdded(summary);
+        mCollectionListener.onEntryInit(summary);
         for (NotificationEntry entry : children) {
-            mCollectionListener.onEntryAdded(entry);
+            mCollectionListener.onEntryInit(entry);
         }
 
         mBeforeFilterListener.onBeforeFinalizeFilter(List.of(groupEntry));
@@ -393,6 +393,70 @@
     }
 
     @Test
+    public void testNullGroupSummary() {
+        // GIVEN a newly-posted group with a summary and two children
+        final GroupEntry group = new GroupEntryBuilder()
+                .setCreationTime(400)
+                .setSummary(getNotificationEntryBuilder().setId(1).build())
+                .addChild(getNotificationEntryBuilder().setId(2).build())
+                .addChild(getNotificationEntryBuilder().setId(3).build())
+                .build();
+        fireAddEvents(List.of(group));
+        final NotificationEntry child0 = group.getChildren().get(0);
+        final NotificationEntry child1 = group.getChildren().get(1);
+        mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
+
+        // WHEN the summary is pruned
+        new GroupEntryBuilder()
+                .setCreationTime(400)
+                .addChild(child0)
+                .addChild(child1)
+                .build();
+
+        // WHEN all of the children (but not the summary) finish inflating
+        mNotifInflater.invokeInflateCallbackForEntry(child0);
+        mNotifInflater.invokeInflateCallbackForEntry(child1);
+
+        // THEN the entire group is not filtered out
+        assertFalse(mUninflatedFilter.shouldFilterOut(child0, 401));
+        assertFalse(mUninflatedFilter.shouldFilterOut(child1, 401));
+    }
+
+    @Test
+    public void testPartiallyInflatedGroupsAreNotFilteredOutIfSummaryReinflate() {
+        // GIVEN a newly-posted group with a summary and two children
+        final String groupKey = "test_reinflate_group";
+        final int summaryId = 1;
+        final GroupEntry group = new GroupEntryBuilder()
+                .setKey(groupKey)
+                .setCreationTime(400)
+                .setSummary(getNotificationEntryBuilder().setId(summaryId).setImportance(1).build())
+                .addChild(getNotificationEntryBuilder().setId(2).build())
+                .addChild(getNotificationEntryBuilder().setId(3).build())
+                .build();
+        fireAddEvents(List.of(group));
+        final NotificationEntry summary = group.getSummary();
+        final NotificationEntry child0 = group.getChildren().get(0);
+        final NotificationEntry child1 = group.getChildren().get(1);
+        mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
+
+        // WHEN all of the children (but not the summary) finish inflating
+        mNotifInflater.invokeInflateCallbackForEntry(child0);
+        mNotifInflater.invokeInflateCallbackForEntry(child1);
+        mNotifInflater.invokeInflateCallbackForEntry(summary);
+
+        // WHEN the summary is updated and starts re-inflating
+        summary.setRanking(new RankingBuilder(summary.getRanking()).setImportance(4).build());
+        fireUpdateEvents(summary);
+        mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
+
+        // THEN the entire group is still not filtered out
+        assertFalse(mUninflatedFilter.shouldFilterOut(summary, 401));
+        assertFalse(mUninflatedFilter.shouldFilterOut(child0, 401));
+        assertFalse(mUninflatedFilter.shouldFilterOut(child1, 401));
+    }
+
+    @Test
     public void testCompletedInflatedGroupsAreReleased() {
         // GIVEN a newly-posted group with a summary and two children
         final GroupEntry group = new GroupEntryBuilder()
@@ -412,7 +476,7 @@
         mNotifInflater.invokeInflateCallbackForEntry(child1);
         mNotifInflater.invokeInflateCallbackForEntry(summary);
 
-        // THEN the entire group is still filtered out
+        // THEN the entire group is no longer filtered out
         assertFalse(mUninflatedFilter.shouldFilterOut(summary, 401));
         assertFalse(mUninflatedFilter.shouldFilterOut(child0, 401));
         assertFalse(mUninflatedFilter.shouldFilterOut(child1, 401));
@@ -494,7 +558,11 @@
 
     private void fireAddEvents(NotificationEntry entry) {
         mCollectionListener.onEntryInit(entry);
-        mCollectionListener.onEntryAdded(entry);
+        mCollectionListener.onEntryInit(entry);
+    }
+
+    private void fireUpdateEvents(NotificationEntry entry) {
+        mCollectionListener.onEntryUpdated(entry);
     }
 
     private static final String TEST_MESSAGE = "TEST_MESSAGE";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt
new file mode 100644
index 0000000..1cdd023
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt
@@ -0,0 +1,210 @@
+/*
+ * 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.statusbar.notification.collection.listbuilder
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.util.Log
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class SemiStableSortTest : SysuiTestCase() {
+
+    var shuffleInput: Boolean = false
+    var testStabilizeTo: Boolean = false
+    var sorter: SemiStableSort? = null
+
+    @Before
+    fun setUp() {
+        shuffleInput = false
+        sorter = null
+    }
+
+    private fun stringStabilizeTo(
+        stableOrder: String,
+        activeOrder: String,
+    ): Pair<String, Boolean> {
+        val actives = activeOrder.toMutableList()
+        val result = mutableListOf<Char>()
+        return (sorter ?: SemiStableSort())
+            .stabilizeTo(
+                actives,
+                { ch -> stableOrder.indexOf(ch).takeIf { it >= 0 } },
+                result,
+            )
+            .let { ordered -> result.joinToString("") to ordered }
+    }
+
+    private fun stringSort(
+        stableOrder: String,
+        activeOrder: String,
+    ): Pair<String, Boolean> {
+        val actives = activeOrder.toMutableList()
+        if (shuffleInput) {
+            actives.shuffle()
+        }
+        return (sorter ?: SemiStableSort())
+            .sort(
+                actives,
+                { ch -> stableOrder.indexOf(ch).takeIf { it >= 0 } },
+                compareBy { activeOrder.indexOf(it) },
+            )
+            .let { ordered -> actives.joinToString("") to ordered }
+    }
+
+    private fun testCase(
+        stableOrder: String,
+        activeOrder: String,
+        expected: String,
+        expectOrdered: Boolean,
+    ) {
+        val (mergeResult, ordered) =
+            if (testStabilizeTo) stringStabilizeTo(stableOrder, activeOrder)
+            else stringSort(stableOrder, activeOrder)
+        val resultPass = expected == mergeResult
+        val orderedPass = ordered == expectOrdered
+        val pass = resultPass && orderedPass
+        val resultSuffix =
+            if (resultPass) "result=$expected" else "expected=$expected got=$mergeResult"
+        val orderedSuffix =
+            if (orderedPass) "ordered=$ordered" else "expected ordered to be $expectOrdered"
+        val readableResult = "stable=$stableOrder active=$activeOrder $resultSuffix $orderedSuffix"
+        Log.d("SemiStableSortTest", "${if (pass) "PASS" else "FAIL"}: $readableResult")
+        if (!pass) {
+            throw AssertionError("Test case failed: $readableResult")
+        }
+    }
+
+    private fun runAllTestCases() {
+        // No input or output
+        testCase("", "", "", true)
+        // Remove everything
+        testCase("ABCDEFG", "", "", true)
+        // Literally no changes
+        testCase("ABCDEFG", "ABCDEFG", "ABCDEFG", true)
+
+        // No stable order
+        testCase("", "ABCDEFG", "ABCDEFG", true)
+
+        // F moved after A, and...
+        testCase("ABCDEFG", "AFBCDEG", "ABCDEFG", false) // No other changes
+        testCase("ABCDEFG", "AXFBCDEG", "AXBCDEFG", false) // Insert X before F
+        testCase("ABCDEFG", "AFXBCDEG", "AXBCDEFG", false) // Insert X after F
+        testCase("ABCDEFG", "AFBCDEXG", "ABCDEFXG", false) // Insert X where F was
+
+        // B moved after F, and...
+        testCase("ABCDEFG", "ACDEFBG", "ABCDEFG", false) // No other changes
+        testCase("ABCDEFG", "ACDEFXBG", "ABCDEFXG", false) // Insert X before B
+        testCase("ABCDEFG", "ACDEFBXG", "ABCDEFXG", false) // Insert X after B
+        testCase("ABCDEFG", "AXCDEFBG", "AXBCDEFG", false) // Insert X where B was
+
+        // Swap F and B, and...
+        testCase("ABCDEFG", "AFCDEBG", "ABCDEFG", false) // No other changes
+        testCase("ABCDEFG", "AXFCDEBG", "AXBCDEFG", false) // Insert X before F
+        testCase("ABCDEFG", "AFXCDEBG", "AXBCDEFG", false) // Insert X after F
+        testCase("ABCDEFG", "AFCXDEBG", "AXBCDEFG", false) // Insert X between CD (Alt: ABCXDEFG)
+        testCase("ABCDEFG", "AFCDXEBG", "ABCDXEFG", false) // Insert X between DE (Alt: ABCDEFXG)
+        testCase("ABCDEFG", "AFCDEXBG", "ABCDEFXG", false) // Insert X before B
+        testCase("ABCDEFG", "AFCDEBXG", "ABCDEFXG", false) // Insert X after B
+
+        // Remove a bunch of entries at once
+        testCase("ABCDEFGHIJKL", "ACEGHI", "ACEGHI", true)
+
+        // Remove a bunch of entries and scramble
+        testCase("ABCDEFGHIJKL", "GCEHAI", "ACEGHI", false)
+
+        // Add a bunch of entries at once
+        testCase("ABCDEFG", "AVBWCXDYZEFG", "AVBWCXDYZEFG", true)
+
+        // Add a bunch of entries and reverse originals
+        // NOTE: Some of these don't have obviously correct answers
+        testCase("ABCDEFG", "GFEBCDAVWXYZ", "ABCDEFGVWXYZ", false) // appended
+        testCase("ABCDEFG", "VWXYZGFEBCDA", "VWXYZABCDEFG", false) // prepended
+        testCase("ABCDEFG", "GFEBVWXYZCDA", "ABCDEFGVWXYZ", false) // closer to back: append
+        testCase("ABCDEFG", "GFEVWXYZBCDA", "VWXYZABCDEFG", false) // closer to front: prepend
+        testCase("ABCDEFG", "GFEVWBXYZCDA", "VWABCDEFGXYZ", false) // split new entries
+
+        // Swap 2 pairs ("*BC*NO*"->"*NO*CB*"), remove EG, add UVWXYZ throughout
+        testCase("ABCDEFGHIJKLMNOP", "AUNOVDFHWXIJKLMYCBZP", "AUVBCDFHWXIJKLMNOYZP", false)
+    }
+
+    @Test
+    fun testSort() {
+        testStabilizeTo = false
+        shuffleInput = false
+        sorter = null
+        runAllTestCases()
+    }
+
+    @Test
+    fun testSortWithSingleInstance() {
+        testStabilizeTo = false
+        shuffleInput = false
+        sorter = SemiStableSort()
+        runAllTestCases()
+    }
+
+    @Test
+    fun testSortWithShuffledInput() {
+        testStabilizeTo = false
+        shuffleInput = true
+        sorter = null
+        runAllTestCases()
+    }
+
+    @Test
+    fun testStabilizeTo() {
+        testStabilizeTo = true
+        sorter = null
+        runAllTestCases()
+    }
+
+    @Test
+    fun testStabilizeToWithSingleInstance() {
+        testStabilizeTo = true
+        sorter = SemiStableSort()
+        runAllTestCases()
+    }
+
+    @Test
+    fun testIsSorted() {
+        val intCmp = Comparator<Int> { x, y -> Integer.compare(x, y) }
+        SemiStableSort.apply {
+            assertTrue(emptyList<Int>().isSorted(intCmp))
+            assertTrue(listOf(1).isSorted(intCmp))
+            assertTrue(listOf(1, 2).isSorted(intCmp))
+            assertTrue(listOf(1, 2, 3).isSorted(intCmp))
+            assertTrue(listOf(1, 2, 3, 4).isSorted(intCmp))
+            assertTrue(listOf(1, 2, 3, 4, 5).isSorted(intCmp))
+            assertTrue(listOf(1, 1, 1, 1, 1).isSorted(intCmp))
+            assertTrue(listOf(1, 1, 2, 2, 3, 3).isSorted(intCmp))
+            assertFalse(listOf(2, 1).isSorted(intCmp))
+            assertFalse(listOf(2, 1, 2).isSorted(intCmp))
+            assertFalse(listOf(1, 2, 1).isSorted(intCmp))
+            assertFalse(listOf(1, 2, 3, 2, 5).isSorted(intCmp))
+            assertFalse(listOf(5, 2, 3, 4, 5).isSorted(intCmp))
+            assertFalse(listOf(1, 2, 3, 4, 1).isSorted(intCmp))
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt
new file mode 100644
index 0000000..2036954
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.statusbar.notification.collection.listbuilder
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderHelper.getContiguousSubLists
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class ShadeListBuilderHelperTest : SysuiTestCase() {
+
+    @Test
+    fun testGetContiguousSubLists() {
+        assertThat(getContiguousSubLists("AAAAAA".toList()) { it })
+            .containsExactly(
+                listOf('A', 'A', 'A', 'A', 'A', 'A'),
+            )
+            .inOrder()
+        assertThat(getContiguousSubLists("AAABBB".toList()) { it })
+            .containsExactly(
+                listOf('A', 'A', 'A'),
+                listOf('B', 'B', 'B'),
+            )
+            .inOrder()
+        assertThat(getContiguousSubLists("AAABAA".toList()) { it })
+            .containsExactly(
+                listOf('A', 'A', 'A'),
+                listOf('B'),
+                listOf('A', 'A'),
+            )
+            .inOrder()
+        assertThat(getContiguousSubLists("AAABAA".toList(), minLength = 2) { it })
+            .containsExactly(
+                listOf('A', 'A', 'A'),
+                listOf('A', 'A'),
+            )
+            .inOrder()
+        assertThat(getContiguousSubLists("AAABBBBCCDEEE".toList()) { it })
+            .containsExactly(
+                listOf('A', 'A', 'A'),
+                listOf('B', 'B', 'B', 'B'),
+                listOf('C', 'C'),
+                listOf('D'),
+                listOf('E', 'E', 'E'),
+            )
+            .inOrder()
+        assertThat(getContiguousSubLists("AAABBBBCCDEEE".toList(), minLength = 2) { it })
+            .containsExactly(
+                listOf('A', 'A', 'A'),
+                listOf('B', 'B', 'B', 'B'),
+                listOf('C', 'C'),
+                listOf('E', 'E', 'E'),
+            )
+            .inOrder()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
index d59cc54..8b7b4de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -29,6 +29,7 @@
 import static com.android.systemui.util.mockito.KotlinMockitoHelpersKt.argThat;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -305,15 +306,59 @@
     }
 
     @Test
-    public void hideSilentNotificationsPerUserSetting() {
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
+    public void hideSilentOnLockscreenSetting() {
+        // GIVEN an 'unfiltered-keyguard-showing' state and notifications shown on lockscreen
+        setupUnfilteredState(mEntry);
         mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+
+        // WHEN the show silent notifs on lockscreen setting is false
         mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
+
+        // WHEN the notification is not high priority and not ambient
+        mEntry = new NotificationEntryBuilder()
+                .setImportance(IMPORTANCE_LOW)
+                .build();
+        when(mHighPriorityProvider.isHighPriority(any())).thenReturn(false);
+
+        // THEN filter out the entry
+        assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+    }
+
+    @Test
+    public void showSilentOnLockscreenSetting() {
+        // GIVEN an 'unfiltered-keyguard-showing' state and notifications shown on lockscreen
+        setupUnfilteredState(mEntry);
+        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+
+        // WHEN the show silent notifs on lockscreen setting is true
+        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true);
+
+        // WHEN the notification is not high priority and not ambient
+        mEntry = new NotificationEntryBuilder()
+                .setImportance(IMPORTANCE_LOW)
+                .build();
+        when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false);
+
+        // THEN do not filter out the entry
+        assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+    }
+
+    @Test
+    public void defaultSilentOnLockscreenSettingIsHide() {
+        // GIVEN an 'unfiltered-keyguard-showing' state and notifications shown on lockscreen
+        setupUnfilteredState(mEntry);
+        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+
+        // WHEN the notification is not high priority and not ambient
         mEntry = new NotificationEntryBuilder()
                 .setUser(new UserHandle(NOTIF_USER_ID))
                 .setImportance(IMPORTANCE_LOW)
                 .build();
         when(mHighPriorityProvider.isHighPriority(any())).thenReturn(false);
+
+        // WhHEN the show silent notifs on lockscreen setting is unset
+        assertNull(mFakeSettings.getString(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS));
+
         assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
     }
 
@@ -431,25 +476,6 @@
     }
 
     @Test
-    public void showSilentOnLockscreenSetting() {
-        // GIVEN an 'unfiltered-keyguard-showing' state
-        setupUnfilteredState(mEntry);
-
-        // WHEN the notification is not high priority and not ambient
-        mEntry.setRanking(new RankingBuilder()
-                .setKey(mEntry.getKey())
-                .setImportance(IMPORTANCE_LOW)
-                .build());
-        when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false);
-
-        // WHEN the show silent notifs on lockscreen setting is true
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true);
-
-        // THEN do not filter out the entry
-        assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
-    }
-
-    @Test
     public void notificationVisibilityPublic() {
         // GIVEN a VISIBILITY_PUBLIC notification
         NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 46f630b..ea311da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -51,12 +51,14 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -97,6 +99,7 @@
     NotifPipelineFlags mFlags;
     @Mock
     KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
+    UiEventLoggerFake mUiEventLoggerFake;
     @Mock
     PendingIntent mPendingIntent;
 
@@ -107,6 +110,8 @@
         MockitoAnnotations.initMocks(this);
         when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(false);
 
+        mUiEventLoggerFake = new UiEventLoggerFake();
+
         mNotifInterruptionStateProvider =
                 new NotificationInterruptStateProviderImpl(
                         mContext.getContentResolver(),
@@ -120,7 +125,8 @@
                         mLogger,
                         mMockHandler,
                         mFlags,
-                        mKeyguardNotificationVisibilityProvider);
+                        mKeyguardNotificationVisibilityProvider,
+                        mUiEventLoggerFake);
         mNotifInterruptionStateProvider.mUseHeadsUp = true;
     }
 
@@ -442,6 +448,13 @@
         verify(mLogger, never()).logNoFullscreen(any(), any());
         verify(mLogger).logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
         verify(mLogger, never()).logFullscreen(any(), any());
+
+        assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1);
+        UiEventLoggerFake.FakeUiEvent fakeUiEvent = mUiEventLoggerFake.get(0);
+        assertThat(fakeUiEvent.eventId).isEqualTo(
+                NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR.getId());
+        assertThat(fakeUiEvent.uid).isEqualTo(entry.getSbn().getUid());
+        assertThat(fakeUiEvent.packageName).isEqualTo(entry.getSbn().getPackageName());
     }
 
     @Test
@@ -600,6 +613,13 @@
         verify(mLogger, never()).logNoFullscreen(any(), any());
         verify(mLogger).logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
         verify(mLogger, never()).logFullscreen(any(), any());
+
+        assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1);
+        UiEventLoggerFake.FakeUiEvent fakeUiEvent = mUiEventLoggerFake.get(0);
+        assertThat(fakeUiEvent.eventId).isEqualTo(
+                NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD.getId());
+        assertThat(fakeUiEvent.uid).isEqualTo(entry.getSbn().getUid());
+        assertThat(fakeUiEvent.packageName).isEqualTo(entry.getSbn().getPackageName());
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt
similarity index 83%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt
index 16e2441..f69839b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt
@@ -28,30 +28,21 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.NotificationUtils
-import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
-class NotificationMemoryMonitorTest : SysuiTestCase() {
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-    }
+class NotificationMemoryMeterTest : SysuiTestCase() {
 
     @Test
     fun currentNotificationMemoryUse_plainNotification() {
         val notification = createBasicNotification().build()
-        val nmm = createNMMWithNotifications(listOf(notification))
-        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        val memoryUse =
+            NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
         assertNotificationObjectSizes(
             memoryUse,
             smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -69,8 +60,8 @@
     fun currentNotificationMemoryUse_plainNotification_dontDoubleCountSameBitmap() {
         val icon = Icon.createWithBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888))
         val notification = createBasicNotification().setLargeIcon(icon).setSmallIcon(icon).build()
-        val nmm = createNMMWithNotifications(listOf(notification))
-        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        val memoryUse =
+            NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
         assertNotificationObjectSizes(
             memoryUse = memoryUse,
             smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -92,8 +83,8 @@
                     RemoteViews(context.packageName, android.R.layout.list_content)
                 )
                 .build()
-        val nmm = createNMMWithNotifications(listOf(notification))
-        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        val memoryUse =
+            NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
         assertNotificationObjectSizes(
             memoryUse = memoryUse,
             smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -112,8 +103,8 @@
         val dataIcon = Icon.createWithData(ByteArray(444444), 0, 444444)
         val notification =
             createBasicNotification().setLargeIcon(dataIcon).setSmallIcon(dataIcon).build()
-        val nmm = createNMMWithNotifications(listOf(notification))
-        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        val memoryUse =
+            NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
         assertNotificationObjectSizes(
             memoryUse = memoryUse,
             smallIcon = 444444,
@@ -141,8 +132,8 @@
                         .bigLargeIcon(bigPictureIcon)
                 )
                 .build()
-        val nmm = createNMMWithNotifications(listOf(notification))
-        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        val memoryUse =
+            NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
         assertNotificationObjectSizes(
             memoryUse = memoryUse,
             smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -167,8 +158,8 @@
             createBasicNotification()
                 .setStyle(Notification.CallStyle.forIncomingCall(person, fakeIntent, fakeIntent))
                 .build()
-        val nmm = createNMMWithNotifications(listOf(notification))
-        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        val memoryUse =
+            NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
         assertNotificationObjectSizes(
             memoryUse = memoryUse,
             smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -203,8 +194,8 @@
                         .addHistoricMessage(historicMessage)
                 )
                 .build()
-        val nmm = createNMMWithNotifications(listOf(notification))
-        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        val memoryUse =
+            NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
         assertNotificationObjectSizes(
             memoryUse = memoryUse,
             smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -225,8 +216,8 @@
         val carIcon = Bitmap.createBitmap(432, 322, Bitmap.Config.ARGB_8888)
         val extender = Notification.CarExtender().setLargeIcon(carIcon)
         val notification = createBasicNotification().extend(extender).build()
-        val nmm = createNMMWithNotifications(listOf(notification))
-        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        val memoryUse =
+            NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
         assertNotificationObjectSizes(
             memoryUse = memoryUse,
             smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -246,8 +237,8 @@
         val wearBackground = Bitmap.createBitmap(443, 433, Bitmap.Config.ARGB_8888)
         val wearExtender = Notification.WearableExtender().setBackground(wearBackground)
         val notification = createBasicNotification().extend(tvExtender).extend(wearExtender).build()
-        val nmm = createNMMWithNotifications(listOf(notification))
-        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        val memoryUse =
+            NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
         assertNotificationObjectSizes(
             memoryUse = memoryUse,
             smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -283,10 +274,10 @@
         extender: Int,
         style: String?,
         styleIcon: Int,
-        hasCustomView: Boolean
+        hasCustomView: Boolean,
     ) {
         assertThat(memoryUse.packageName).isEqualTo("test_pkg")
-        assertThat(memoryUse.notificationId)
+        assertThat(memoryUse.notificationKey)
             .isEqualTo(NotificationUtils.logKey("0|test_pkg|0|test|0"))
         assertThat(memoryUse.objectUsage.smallIcon).isEqualTo(smallIcon)
         assertThat(memoryUse.objectUsage.largeIcon).isEqualTo(largeIcon)
@@ -301,21 +292,14 @@
     }
 
     private fun getUseObject(
-        singleItemUseList: List<NotificationMemoryUsage>
+        singleItemUseList: List<NotificationMemoryUsage>,
     ): NotificationMemoryUsage {
         assertThat(singleItemUseList).hasSize(1)
         return singleItemUseList[0]
     }
 
-    private fun createNMMWithNotifications(
-        notifications: List<Notification>
-    ): NotificationMemoryMonitor {
-        val notifPipeline: NotifPipeline = mock()
-        val notificationEntries =
-            notifications.map { n ->
-                NotificationEntryBuilder().setTag("test").setNotification(n).build()
-            }
-        whenever(notifPipeline.allNotifs).thenReturn(notificationEntries)
-        return NotificationMemoryMonitor(notifPipeline, mock())
-    }
+    private fun createNotificationEntry(
+        notification: Notification,
+    ): NotificationEntry =
+        NotificationEntryBuilder().setTag("test").setNotification(notification).build()
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
new file mode 100644
index 0000000..3a16fb3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
@@ -0,0 +1,148 @@
+package com.android.systemui.statusbar.notification.logging
+
+import android.app.Notification
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.widget.RemoteViews
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.tests.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class NotificationMemoryViewWalkerTest : SysuiTestCase() {
+
+    private lateinit var testHelper: NotificationTestHelper
+
+    @Before
+    fun setUp() {
+        allowTestableLooperAsMainThread()
+        testHelper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+    }
+
+    @Test
+    fun testViewWalker_nullRow_returnsEmptyView() {
+        val result = NotificationMemoryViewWalker.getViewUsage(null)
+        assertThat(result).isNotNull()
+        assertThat(result).isEmpty()
+    }
+
+    @Test
+    fun testViewWalker_plainNotification() {
+        val row = testHelper.createRow()
+        val result = NotificationMemoryViewWalker.getViewUsage(row)
+        assertThat(result).hasSize(5)
+        assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
+        assertThat(result)
+            .contains(NotificationViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, 0, 0, 0, 0, 0, 0))
+        assertThat(result)
+            .contains(NotificationViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, 0, 0, 0, 0, 0, 0))
+        assertThat(result)
+            .contains(NotificationViewUsage(ViewType.PRIVATE_CONTRACTED_VIEW, 0, 0, 0, 0, 0, 0))
+        assertThat(result)
+            .contains(NotificationViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, 0, 0, 0, 0, 0, 0))
+    }
+
+    @Test
+    fun testViewWalker_bigPictureNotification() {
+        val bigPicture = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)
+        val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
+        val largeIcon = Icon.createWithBitmap(Bitmap.createBitmap(60, 60, Bitmap.Config.ARGB_8888))
+        val row =
+            testHelper.createRow(
+                Notification.Builder(mContext)
+                    .setContentText("Test")
+                    .setContentTitle("title")
+                    .setSmallIcon(icon)
+                    .setLargeIcon(largeIcon)
+                    .setStyle(Notification.BigPictureStyle().bigPicture(bigPicture))
+                    .build()
+            )
+        val result = NotificationMemoryViewWalker.getViewUsage(row)
+        assertThat(result).hasSize(5)
+        assertThat(result)
+            .contains(
+                NotificationViewUsage(
+                    ViewType.PRIVATE_EXPANDED_VIEW,
+                    icon.bitmap.allocationByteCount,
+                    largeIcon.bitmap.allocationByteCount,
+                    0,
+                    bigPicture.allocationByteCount,
+                    0,
+                    bigPicture.allocationByteCount +
+                        icon.bitmap.allocationByteCount +
+                        largeIcon.bitmap.allocationByteCount
+                )
+            )
+
+        assertThat(result)
+            .contains(
+                NotificationViewUsage(
+                    ViewType.PRIVATE_CONTRACTED_VIEW,
+                    icon.bitmap.allocationByteCount,
+                    largeIcon.bitmap.allocationByteCount,
+                    0,
+                    0,
+                    0,
+                    icon.bitmap.allocationByteCount + largeIcon.bitmap.allocationByteCount
+                )
+            )
+        // Due to deduplication, this should all be 0.
+        assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
+    }
+
+    @Test
+    fun testViewWalker_customView() {
+        val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
+        val bitmap = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)
+
+        val views = RemoteViews(mContext.packageName, R.layout.custom_view_dark)
+        views.setImageViewBitmap(R.id.custom_view_dark_image, bitmap)
+        val row =
+            testHelper.createRow(
+                Notification.Builder(mContext)
+                    .setContentText("Test")
+                    .setContentTitle("title")
+                    .setSmallIcon(icon)
+                    .setCustomContentView(views)
+                    .setCustomBigContentView(views)
+                    .build()
+            )
+        val result = NotificationMemoryViewWalker.getViewUsage(row)
+        assertThat(result).hasSize(5)
+        assertThat(result)
+            .contains(
+                NotificationViewUsage(
+                    ViewType.PRIVATE_CONTRACTED_VIEW,
+                    icon.bitmap.allocationByteCount,
+                    0,
+                    0,
+                    0,
+                    bitmap.allocationByteCount,
+                    bitmap.allocationByteCount + icon.bitmap.allocationByteCount
+                )
+            )
+        assertThat(result)
+            .contains(
+                NotificationViewUsage(
+                    ViewType.PRIVATE_EXPANDED_VIEW,
+                    icon.bitmap.allocationByteCount,
+                    0,
+                    0,
+                    0,
+                    bitmap.allocationByteCount,
+                    bitmap.allocationByteCount + icon.bitmap.allocationByteCount
+                )
+            )
+        // Due to deduplication, this should all be 0.
+        assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 8375e7c..5394d88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -51,7 +51,7 @@
 import androidx.test.filters.Suppress;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.MediaFeatureFlag;
+import com.android.systemui.media.controls.util.MediaFeatureFlag;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 9abdeb9..421f918 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -52,7 +52,7 @@
 import com.android.systemui.TestableDependency;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.media.MediaFeatureFlag;
+import com.android.systemui.media.controls.util.MediaFeatureFlag;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationMediaManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
index 11798a7..87f4c32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
@@ -361,6 +361,22 @@
         assertThat(sut.isOnKeyguard).isFalse()
     }
     // endregion
+
+    // region mIsClosing
+    @Test
+    fun isClosing_whenShadeClosing_shouldReturnTrue() {
+        sut.setIsClosing(true)
+
+        assertThat(sut.isClosing).isTrue()
+    }
+
+    @Test
+    fun isClosing_whenShadeFinishClosing_shouldReturnFalse() {
+        sut.setIsClosing(false)
+
+        assertThat(sut.isClosing).isFalse()
+    }
+    // endregion
 }
 
 // region Arrange helper methods.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index a95a49c..8c8b644 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -147,8 +147,8 @@
                 createSection(mFirst, mSecond),
                 createSection(null, null)
         });
-        Assert.assertEquals(1.0f, mSecond.getCurrentBottomRoundness(), 0.0f);
-        Assert.assertEquals(mSmallRadiusRatio, mSecond.getCurrentTopRoundness(), 0.0f);
+        Assert.assertEquals(1.0f, mSecond.getBottomRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, mSecond.getTopRoundness(), 0.0f);
     }
 
     @Test
@@ -170,13 +170,13 @@
         when(testHelper.getStatusBarStateController().isDozing()).thenReturn(true);
         row.setHeadsUp(true);
         mRoundnessManager.updateView(entry.getRow(), false);
-        Assert.assertEquals(1f, row.getCurrentBottomRoundness(), 0.0f);
-        Assert.assertEquals(1f, row.getCurrentTopRoundness(), 0.0f);
+        Assert.assertEquals(1f, row.getBottomRoundness(), 0.0f);
+        Assert.assertEquals(1f, row.getTopRoundness(), 0.0f);
 
         row.setHeadsUp(false);
         mRoundnessManager.updateView(entry.getRow(), false);
-        Assert.assertEquals(mSmallRadiusRatio, row.getCurrentBottomRoundness(), 0.0f);
-        Assert.assertEquals(mSmallRadiusRatio, row.getCurrentTopRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, row.getBottomRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, row.getTopRoundness(), 0.0f);
     }
 
     @Test
@@ -185,8 +185,8 @@
                 createSection(mFirst, mFirst),
                 createSection(null, mSecond)
         });
-        Assert.assertEquals(1.0f, mSecond.getCurrentBottomRoundness(), 0.0f);
-        Assert.assertEquals(mSmallRadiusRatio, mSecond.getCurrentTopRoundness(), 0.0f);
+        Assert.assertEquals(1.0f, mSecond.getBottomRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, mSecond.getTopRoundness(), 0.0f);
     }
 
     @Test
@@ -195,8 +195,8 @@
                 createSection(mFirst, mFirst),
                 createSection(mSecond, null)
         });
-        Assert.assertEquals(mSmallRadiusRatio, mSecond.getCurrentBottomRoundness(), 0.0f);
-        Assert.assertEquals(1.0f, mSecond.getCurrentTopRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, mSecond.getBottomRoundness(), 0.0f);
+        Assert.assertEquals(1.0f, mSecond.getTopRoundness(), 0.0f);
     }
 
     @Test
@@ -205,8 +205,8 @@
                 createSection(mFirst, null),
                 createSection(null, null)
         });
-        Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f);
-        Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, mFirst.getBottomRoundness(), 0.0f);
+        Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f);
     }
 
     @Test
@@ -215,8 +215,8 @@
                 createSection(mSecond, mSecond),
                 createSection(null, null)
         });
-        Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f);
-        Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentTopRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, mFirst.getBottomRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, mFirst.getTopRoundness(), 0.0f);
     }
 
     @Test
@@ -226,8 +226,8 @@
                 createSection(mSecond, mSecond),
                 createSection(null, null)
         });
-        Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f);
-        Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f);
+        Assert.assertEquals(1.0f, mFirst.getBottomRoundness(), 0.0f);
+        Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f);
     }
 
     @Test
@@ -238,8 +238,8 @@
                 createSection(mSecond, mSecond),
                 createSection(null, null)
         });
-        Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f);
-        Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f);
+        Assert.assertEquals(1.0f, mFirst.getBottomRoundness(), 0.0f);
+        Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f);
     }
 
     @Test
@@ -250,8 +250,8 @@
                 createSection(mSecond, mSecond),
                 createSection(null, null)
         });
-        Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f);
-        Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f);
+        Assert.assertEquals(1.0f, mFirst.getBottomRoundness(), 0.0f);
+        Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f);
     }
 
     @Test
@@ -262,8 +262,8 @@
                 createSection(mSecond, mSecond),
                 createSection(null, null)
         });
-        Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f);
-        Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentTopRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, mFirst.getBottomRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, mFirst.getTopRoundness(), 0.0f);
     }
 
     @Test
@@ -274,8 +274,8 @@
                 createSection(mSecond, mSecond),
                 createSection(null, null)
         });
-        Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f);
-        Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f);
+        Assert.assertEquals(1.0f, mFirst.getBottomRoundness(), 0.0f);
+        Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f);
     }
 
     @Test
@@ -286,8 +286,8 @@
                 createSection(mSecond, mSecond),
                 createSection(null, null)
         });
-        Assert.assertEquals(0.5f, mFirst.getCurrentBottomRoundness(), 0.0f);
-        Assert.assertEquals(0.5f, mFirst.getCurrentTopRoundness(), 0.0f);
+        Assert.assertEquals(0.5f, mFirst.getBottomRoundness(), 0.0f);
+        Assert.assertEquals(0.5f, mFirst.getTopRoundness(), 0.0f);
     }
 
     @Test
@@ -298,8 +298,8 @@
                 createSection(null, null)
         });
         mFirst.setHeadsUpAnimatingAway(true);
-        Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f);
-        Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f);
+        Assert.assertEquals(1.0f, mFirst.getBottomRoundness(), 0.0f);
+        Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f);
     }
 
 
@@ -312,8 +312,8 @@
         });
         mFirst.setHeadsUpAnimatingAway(true);
         mFirst.setHeadsUpAnimatingAway(false);
-        Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f);
-        Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentTopRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, mFirst.getBottomRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, mFirst.getTopRoundness(), 0.0f);
     }
 
     @Test
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 9d848e8..ecc0224 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.KeyguardMediaController;
+import com.android.systemui.media.controls.ui.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 1c9b0be..90061b0 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
@@ -46,7 +46,7 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.KeyguardMediaController;
+import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -129,6 +129,7 @@
     @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
     @Mock private ShadeTransitionController mShadeTransitionController;
     @Mock private FeatureFlags mFeatureFlags;
+    @Mock private NotificationTargetsHelper mNotificationTargetsHelper;
 
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
@@ -177,7 +178,8 @@
                 mStackLogger,
                 mLogger,
                 mNotificationStackSizeCalculator,
-                mFeatureFlags
+                mFeatureFlags,
+                mNotificationTargetsHelper
         );
 
         when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 4353036..91aecd8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -163,7 +163,7 @@
         mStackScroller.setCentralSurfaces(mCentralSurfaces);
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true);
-        when(mStackScrollLayoutController.getNoticationRoundessManager())
+        when(mStackScrollLayoutController.getNotificationRoundnessManager())
                 .thenReturn(mNotificationRoundnessManager);
         mStackScroller.setController(mStackScrollLayoutController);
 
@@ -728,6 +728,57 @@
         verify(mNotificationStackSizeCalculator).computeHeight(any(), anyInt(), anyFloat());
     }
 
+    @Test
+    public void testSetOwnScrollY_shadeNotClosing_scrollYChanges() {
+        // Given: shade is not closing, scrollY is 0
+        mAmbientState.setScrollY(0);
+        assertEquals(0, mAmbientState.getScrollY());
+        mAmbientState.setIsClosing(false);
+
+        // When: call NotificationStackScrollLayout.setOwnScrollY to set scrollY to 1
+        mStackScroller.setOwnScrollY(1);
+
+        // Then: scrollY should be set to 1
+        assertEquals(1, mAmbientState.getScrollY());
+
+        // Reset scrollY back to 0 to avoid interfering with other tests
+        mStackScroller.setOwnScrollY(0);
+        assertEquals(0, mAmbientState.getScrollY());
+    }
+
+    @Test
+    public void testSetOwnScrollY_shadeClosing_scrollYDoesNotChange() {
+        // Given: shade is closing, scrollY is 0
+        mAmbientState.setScrollY(0);
+        assertEquals(0, mAmbientState.getScrollY());
+        mAmbientState.setIsClosing(true);
+
+        // When: call NotificationStackScrollLayout.setOwnScrollY to set scrollY to 1
+        mStackScroller.setOwnScrollY(1);
+
+        // Then: scrollY should not change, it should still be 0
+        assertEquals(0, mAmbientState.getScrollY());
+
+        // Reset scrollY and mAmbientState.mIsClosing to avoid interfering with other tests
+        mAmbientState.setIsClosing(false);
+        mStackScroller.setOwnScrollY(0);
+        assertEquals(0, mAmbientState.getScrollY());
+    }
+
+    @Test
+    public void onShadeFlingClosingEnd_scrollYShouldBeSetToZero() {
+        // Given: mAmbientState.mIsClosing is set to be true
+        // mIsExpanded is set to be false
+        mAmbientState.setIsClosing(true);
+        mStackScroller.setIsExpanded(false);
+
+        // When: onExpansionStopped is called
+        mStackScroller.onExpansionStopped();
+
+        // Then: mAmbientState.scrollY should be set to be 0
+        assertEquals(mAmbientState.getScrollY(), 0);
+    }
+
     private void setBarStateForTest(int state) {
         // Can't inject this through the listener or we end up on the actual implementation
         // rather than the mock because the spy just coppied the anonymous inner /shruggie.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
new file mode 100644
index 0000000..a2e9230
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
@@ -0,0 +1,107 @@
+package com.android.systemui.statusbar.notification.stack
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.util.mockito.mock
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Tests for {@link NotificationTargetsHelper}. */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationTargetsHelperTest : SysuiTestCase() {
+    lateinit var notificationTestHelper: NotificationTestHelper
+    private val sectionsManager: NotificationSectionsManager = mock()
+    private val stackScrollLayout: NotificationStackScrollLayout = mock()
+
+    @Before
+    fun setUp() {
+        allowTestableLooperAsMainThread()
+        notificationTestHelper =
+            NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+    }
+
+    private fun notificationTargetsHelper(
+        notificationGroupCorner: Boolean = true,
+    ) =
+        NotificationTargetsHelper(
+            FakeFeatureFlags().apply {
+                set(Flags.NOTIFICATION_GROUP_CORNER, notificationGroupCorner)
+            }
+        )
+
+    @Test
+    fun targetsForFirstNotificationInGroup() {
+        val children = notificationTestHelper.createGroup(3).childrenContainer
+        val swiped = children.attachedChildren[0]
+
+        val actual =
+            notificationTargetsHelper()
+                .findRoundableTargets(
+                    viewSwiped = swiped,
+                    stackScrollLayout = stackScrollLayout,
+                    sectionsManager = sectionsManager,
+                )
+
+        val expected =
+            RoundableTargets(
+                before = children.notificationHeaderWrapper, // group header
+                swiped = swiped,
+                after = children.attachedChildren[1],
+            )
+        assertEquals(expected, actual)
+    }
+
+    @Test
+    fun targetsForMiddleNotificationInGroup() {
+        val children = notificationTestHelper.createGroup(3).childrenContainer
+        val swiped = children.attachedChildren[1]
+
+        val actual =
+            notificationTargetsHelper()
+                .findRoundableTargets(
+                    viewSwiped = swiped,
+                    stackScrollLayout = stackScrollLayout,
+                    sectionsManager = sectionsManager,
+                )
+
+        val expected =
+            RoundableTargets(
+                before = children.attachedChildren[0],
+                swiped = swiped,
+                after = children.attachedChildren[2],
+            )
+        assertEquals(expected, actual)
+    }
+
+    @Test
+    fun targetsForLastNotificationInGroup() {
+        val children = notificationTestHelper.createGroup(3).childrenContainer
+        val swiped = children.attachedChildren[2]
+
+        val actual =
+            notificationTargetsHelper()
+                .findRoundableTargets(
+                    viewSwiped = swiped,
+                    stackScrollLayout = stackScrollLayout,
+                    sectionsManager = sectionsManager,
+                )
+
+        val expected =
+            RoundableTargets(
+                before = children.attachedChildren[1],
+                swiped = swiped,
+                after = null,
+            )
+        assertEquals(expected, actual)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index ad497a2..6de8bd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -80,6 +80,7 @@
 
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.logging.testing.FakeMetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
@@ -98,7 +99,8 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -271,7 +273,6 @@
     @Mock private OngoingCallController mOngoingCallController;
     @Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
     @Mock private LockscreenShadeTransitionController mLockscreenTransitionController;
-    @Mock private FeatureFlags mFeatureFlags;
     @Mock private NotificationVisibilityProvider mVisibilityProvider;
     @Mock private WallpaperManager mWallpaperManager;
     @Mock private IWallpaperManager mIWallpaperManager;
@@ -296,9 +297,10 @@
 
     private ShadeController mShadeController;
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
-    private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
-    private FakeExecutor mUiBgExecutor = new FakeExecutor(mFakeSystemClock);
-    private InitController mInitController = new InitController();
+    private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
+    private final FakeExecutor mUiBgExecutor = new FakeExecutor(mFakeSystemClock);
+    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+    private final InitController mInitController = new InitController();
     private final DumpManager mDumpManager = new DumpManager();
 
     @Before
@@ -322,7 +324,8 @@
                         mock(NotificationInterruptLogger.class),
                         new Handler(TestableLooper.get(this).getLooper()),
                         mock(NotifPipelineFlags.class),
-                        mock(KeyguardNotificationVisibilityProvider.class));
+                        mock(KeyguardNotificationVisibilityProvider.class),
+                        mock(UiEventLogger.class));
 
         mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
         mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));
@@ -1017,6 +1020,60 @@
     }
 
     @Test
+    public void collapseShade_callsAnimateCollapsePanels_whenExpanded() {
+        // GIVEN the shade is expanded
+        mCentralSurfaces.setPanelExpanded(true);
+        mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
+
+        // WHEN collapseShade is called
+        mCentralSurfaces.collapseShade();
+
+        // VERIFY that animateCollapsePanels is called
+        verify(mShadeController).animateCollapsePanels();
+    }
+
+    @Test
+    public void collapseShade_doesNotCallAnimateCollapsePanels_whenCollapsed() {
+        // GIVEN the shade is collapsed
+        mCentralSurfaces.setPanelExpanded(false);
+        mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
+
+        // WHEN collapseShade is called
+        mCentralSurfaces.collapseShade();
+
+        // VERIFY that animateCollapsePanels is NOT called
+        verify(mShadeController, never()).animateCollapsePanels();
+    }
+
+    @Test
+    public void collapseShadeForBugReport_callsAnimateCollapsePanels_whenFlagDisabled() {
+        // GIVEN the shade is expanded & flag enabled
+        mCentralSurfaces.setPanelExpanded(true);
+        mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
+        mFeatureFlags.set(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT, false);
+
+        // WHEN collapseShadeForBugreport is called
+        mCentralSurfaces.collapseShadeForBugreport();
+
+        // VERIFY that animateCollapsePanels is called
+        verify(mShadeController).animateCollapsePanels();
+    }
+
+    @Test
+    public void collapseShadeForBugReport_doesNotCallAnimateCollapsePanels_whenFlagEnabled() {
+        // GIVEN the shade is expanded & flag enabled
+        mCentralSurfaces.setPanelExpanded(true);
+        mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
+        mFeatureFlags.set(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT, true);
+
+        // WHEN collapseShadeForBugreport is called
+        mCentralSurfaces.collapseShadeForBugreport();
+
+        // VERIFY that animateCollapsePanels is called
+        verify(mShadeController, never()).animateCollapsePanels();
+    }
+
+    @Test
     public void deviceStateChange_unfolded_shadeOpen_setsLeaveOpenOnKeyguardHide() {
         when(mKeyguardStateController.isShowing()).thenReturn(false);
         setFoldedStates(FOLD_STATE_FOLDED);
@@ -1102,7 +1159,8 @@
                 NotificationInterruptLogger logger,
                 Handler mainHandler,
                 NotifPipelineFlags flags,
-                KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider) {
+                KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
+                UiEventLogger uiEventLogger) {
             super(
                     contentResolver,
                     powerManager,
@@ -1115,7 +1173,8 @@
                     logger,
                     mainHandler,
                     flags,
-                    keyguardNotificationVisibilityProvider
+                    keyguardNotificationVisibilityProvider,
+                    uiEventLogger
             );
             mUseHeadsUp = true;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 8da8d04..0c35659 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -117,7 +117,6 @@
     @Mock private BouncerCallbackInteractor mBouncerCallbackInteractor;
     @Mock private BouncerInteractor mBouncerInteractor;
     @Mock private BouncerView mBouncerView;
-//    @Mock private WeakReference<BouncerViewDelegate> mBouncerViewDelegateWeakReference;
     @Mock private BouncerViewDelegate mBouncerViewDelegate;
 
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
index 1ee8875..78a4db1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.plugins.log.LogcatEchoTracker
 import com.android.systemui.statusbar.DisableFlagsLogger
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 63467e7..438271c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -49,9 +49,9 @@
 import com.android.systemui.SysuiBaseFragmentTest;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.LogcatEchoTracker;
 import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.plugins.log.LogcatEchoTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.ShadeExpansionStateManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
index bf43238..eba3b04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
@@ -20,7 +20,6 @@
 import android.os.UserHandle
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
-import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
@@ -34,8 +33,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -91,7 +90,7 @@
     fun testStartActivity() {
         `when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(false)
         statusBarUserSwitcherContainer.callOnClick()
-        verify(userSwitcherDialogController).showDialog(any(View::class.java))
+        verify(userSwitcherDialogController).showDialog(any(), any())
         `when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(true)
         statusBarUserSwitcherContainer.callOnClick()
         verify(activityStarter).startActivity(any(Intent::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
new file mode 100644
index 0000000..b7a6c01
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
@@ -0,0 +1,116 @@
+/*
+ * 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.statusbar.pipeline.airplane.data.repository
+
+import android.os.Handler
+import android.os.Looper
+import android.os.UserHandle
+import android.provider.Settings.Global
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class AirplaneModeRepositoryImplTest : SysuiTestCase() {
+
+    private lateinit var underTest: AirplaneModeRepositoryImpl
+
+    @Mock private lateinit var logger: ConnectivityPipelineLogger
+    private lateinit var bgHandler: Handler
+    private lateinit var scope: CoroutineScope
+    private lateinit var settings: FakeSettings
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        bgHandler = Handler(Looper.getMainLooper())
+        scope = CoroutineScope(IMMEDIATE)
+        settings = FakeSettings()
+        settings.userId = UserHandle.USER_ALL
+
+        underTest =
+            AirplaneModeRepositoryImpl(
+                bgHandler,
+                settings,
+                logger,
+                scope,
+            )
+    }
+
+    @After
+    fun tearDown() {
+        scope.cancel()
+    }
+
+    @Test
+    fun isAirplaneMode_initiallyGetsSettingsValue() =
+        runBlocking(IMMEDIATE) {
+            settings.putInt(Global.AIRPLANE_MODE_ON, 1)
+
+            underTest =
+                AirplaneModeRepositoryImpl(
+                    bgHandler,
+                    settings,
+                    logger,
+                    scope,
+                )
+
+            val job = underTest.isAirplaneMode.launchIn(this)
+
+            assertThat(underTest.isAirplaneMode.value).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isAirplaneMode_settingUpdated_valueUpdated() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.isAirplaneMode.launchIn(this)
+
+            settings.putInt(Global.AIRPLANE_MODE_ON, 0)
+            yield()
+            assertThat(underTest.isAirplaneMode.value).isFalse()
+
+            settings.putInt(Global.AIRPLANE_MODE_ON, 1)
+            yield()
+            assertThat(underTest.isAirplaneMode.value).isTrue()
+
+            settings.putInt(Global.AIRPLANE_MODE_ON, 0)
+            yield()
+            assertThat(underTest.isAirplaneMode.value).isFalse()
+
+            job.cancel()
+        }
+}
+
+private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
new file mode 100644
index 0000000..63bbdfc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.statusbar.pipeline.airplane.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class FakeAirplaneModeRepository : AirplaneModeRepository {
+    private val _isAirplaneMode = MutableStateFlow(false)
+    override val isAirplaneMode: StateFlow<Boolean> = _isAirplaneMode
+
+    fun setIsAirplaneMode(isAirplaneMode: Boolean) {
+        _isAirplaneMode.value = isAirplaneMode
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorTest.kt
new file mode 100644
index 0000000..33a80e1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorTest.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.statusbar.pipeline.airplane.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+class AirplaneModeInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: AirplaneModeInteractor
+
+    private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
+    private lateinit var connectivityRepository: FakeConnectivityRepository
+
+    @Before
+    fun setUp() {
+        airplaneModeRepository = FakeAirplaneModeRepository()
+        connectivityRepository = FakeConnectivityRepository()
+        underTest = AirplaneModeInteractor(airplaneModeRepository, connectivityRepository)
+    }
+
+    @Test
+    fun isAirplaneMode_matchesRepo() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.isAirplaneMode.onEach { latest = it }.launchIn(this)
+
+            airplaneModeRepository.setIsAirplaneMode(true)
+            yield()
+            assertThat(latest).isTrue()
+
+            airplaneModeRepository.setIsAirplaneMode(false)
+            yield()
+            assertThat(latest).isFalse()
+
+            airplaneModeRepository.setIsAirplaneMode(true)
+            yield()
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isForceHidden_repoHasWifiHidden_outputsTrue() =
+        runBlocking(IMMEDIATE) {
+            connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.AIRPLANE))
+
+            var latest: Boolean? = null
+            val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isForceHidden_repoDoesNotHaveWifiHidden_outputsFalse() =
+        runBlocking(IMMEDIATE) {
+            connectivityRepository.setForceHiddenIcons(setOf())
+
+            var latest: Boolean? = null
+            val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+}
+
+private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt
new file mode 100644
index 0000000..76016a1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.statusbar.pipeline.airplane.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+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.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+class AirplaneModeViewModelTest : SysuiTestCase() {
+
+    private lateinit var underTest: AirplaneModeViewModel
+
+    @Mock private lateinit var logger: ConnectivityPipelineLogger
+    private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
+    private lateinit var connectivityRepository: FakeConnectivityRepository
+    private lateinit var interactor: AirplaneModeInteractor
+    private lateinit var scope: CoroutineScope
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        airplaneModeRepository = FakeAirplaneModeRepository()
+        connectivityRepository = FakeConnectivityRepository()
+        interactor = AirplaneModeInteractor(airplaneModeRepository, connectivityRepository)
+        scope = CoroutineScope(IMMEDIATE)
+
+        underTest =
+            AirplaneModeViewModel(
+                interactor,
+                logger,
+                scope,
+            )
+    }
+
+    @Test
+    fun isAirplaneModeIconVisible_notAirplaneMode_outputsFalse() =
+        runBlocking(IMMEDIATE) {
+            connectivityRepository.setForceHiddenIcons(setOf())
+            airplaneModeRepository.setIsAirplaneMode(false)
+
+            var latest: Boolean? = null
+            val job = underTest.isAirplaneModeIconVisible.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isAirplaneModeIconVisible_forceHidden_outputsFalse() =
+        runBlocking(IMMEDIATE) {
+            connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.AIRPLANE))
+            airplaneModeRepository.setIsAirplaneMode(true)
+
+            var latest: Boolean? = null
+            val job = underTest.isAirplaneModeIconVisible.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isAirplaneModeIconVisible_isAirplaneModeAndNotForceHidden_outputsTrue() =
+        runBlocking(IMMEDIATE) {
+            connectivityRepository.setForceHiddenIcons(setOf())
+            airplaneModeRepository.setIsAirplaneMode(true)
+
+            var latest: Boolean? = null
+            val job = underTest.isAirplaneModeIconVisible.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+}
+
+private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
index 0e75c74..b32058f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
@@ -22,7 +22,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.plugins.log.LogcatEchoTracker
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
 import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
index f751afc..2f18ce3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -27,6 +27,9 @@
     private val _isWifiEnabled: MutableStateFlow<Boolean> = MutableStateFlow(false)
     override val isWifiEnabled: StateFlow<Boolean> = _isWifiEnabled
 
+    private val _isWifiDefault: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    override val isWifiDefault: StateFlow<Boolean> = _isWifiDefault
+
     private val _wifiNetwork: MutableStateFlow<WifiNetworkModel> =
         MutableStateFlow(WifiNetworkModel.Inactive)
     override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
@@ -38,6 +41,10 @@
         _isWifiEnabled.value = enabled
     }
 
+    fun setIsWifiDefault(default: Boolean) {
+        _isWifiDefault.value = default
+    }
+
     fun setWifiNetwork(wifiNetworkModel: WifiNetworkModel) {
         _wifiNetwork.value = wifiNetworkModel
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
index 0ba0bd6..a64a4bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
@@ -222,6 +222,83 @@
     }
 
     @Test
+    fun isWifiDefault_initiallyGetsDefault() = runBlocking(IMMEDIATE) {
+        val job = underTest.isWifiDefault.launchIn(this)
+
+        assertThat(underTest.isWifiDefault.value).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun isWifiDefault_wifiNetwork_isTrue() = runBlocking(IMMEDIATE) {
+        val job = underTest.isWifiDefault.launchIn(this)
+
+        val wifiInfo = mock<WifiInfo>().apply {
+            whenever(this.ssid).thenReturn(SSID)
+        }
+
+        getDefaultNetworkCallback().onCapabilitiesChanged(
+            NETWORK,
+            createWifiNetworkCapabilities(wifiInfo)
+        )
+
+        assertThat(underTest.isWifiDefault.value).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
+    fun isWifiDefault_cellularVcnNetwork_isTrue() = runBlocking(IMMEDIATE) {
+        val job = underTest.isWifiDefault.launchIn(this)
+
+        val capabilities = mock<NetworkCapabilities>().apply {
+            whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+            whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
+        }
+
+        getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+        assertThat(underTest.isWifiDefault.value).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
+    fun isWifiDefault_cellularNotVcnNetwork_isFalse() = runBlocking(IMMEDIATE) {
+        val job = underTest.isWifiDefault.launchIn(this)
+
+        val capabilities = mock<NetworkCapabilities>().apply {
+            whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+            whenever(this.transportInfo).thenReturn(mock())
+        }
+
+        getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+        assertThat(underTest.isWifiDefault.value).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun isWifiDefault_wifiNetworkLost_isFalse() = runBlocking(IMMEDIATE) {
+        val job = underTest.isWifiDefault.launchIn(this)
+
+        // First, add a network
+        getDefaultNetworkCallback()
+            .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
+        assertThat(underTest.isWifiDefault.value).isTrue()
+
+        // WHEN the network is lost
+        getDefaultNetworkCallback().onLost(NETWORK)
+
+        // THEN we update to false
+        assertThat(underTest.isWifiDefault.value).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
     fun wifiNetwork_initiallyGetsDefault() = runBlocking(IMMEDIATE) {
         var latest: WifiNetworkModel? = null
         val job = underTest
@@ -745,6 +822,12 @@
         return callbackCaptor.value!!
     }
 
+    private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
+        val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
+        verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
+        return callbackCaptor.value!!
+    }
+
     private fun createWifiNetworkCapabilities(
         wifiInfo: WifiInfo,
         isValidated: Boolean = true,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
index 39b886a..71b8bab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
@@ -178,6 +178,29 @@
     }
 
     @Test
+    fun isDefault_matchesRepoIsDefault() = runBlocking(IMMEDIATE) {
+        var latest: Boolean? = null
+        val job = underTest
+            .isDefault
+            .onEach { latest = it }
+            .launchIn(this)
+
+        wifiRepository.setIsWifiDefault(true)
+        yield()
+        assertThat(latest).isTrue()
+
+        wifiRepository.setIsWifiDefault(false)
+        yield()
+        assertThat(latest).isFalse()
+
+        wifiRepository.setIsWifiDefault(true)
+        yield()
+        assertThat(latest).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
     fun wifiNetwork_matchesRepoWifiNetwork() = runBlocking(IMMEDIATE) {
         val wifiNetwork = WifiNetworkModel.Active(
             networkId = 45,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 4efb135..c584109 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -30,6 +30,9 @@
 import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
 import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+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.airplane.ui.viewmodel.AirplaneModeViewModel
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
@@ -63,11 +66,13 @@
     private lateinit var connectivityConstants: ConnectivityConstants
     @Mock
     private lateinit var wifiConstants: WifiConstants
+    private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
     private lateinit var connectivityRepository: FakeConnectivityRepository
     private lateinit var wifiRepository: FakeWifiRepository
     private lateinit var interactor: WifiInteractor
     private lateinit var viewModel: WifiViewModel
     private lateinit var scope: CoroutineScope
+    private lateinit var airplaneModeViewModel: AirplaneModeViewModel
 
     @JvmField @Rule
     val instantTaskExecutor = InstantTaskExecutorRule()
@@ -77,12 +82,22 @@
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
 
+        airplaneModeRepository = FakeAirplaneModeRepository()
         connectivityRepository = FakeConnectivityRepository()
         wifiRepository = FakeWifiRepository()
         wifiRepository.setIsWifiEnabled(true)
         interactor = WifiInteractor(connectivityRepository, wifiRepository)
         scope = CoroutineScope(Dispatchers.Unconfined)
+        airplaneModeViewModel = AirplaneModeViewModel(
+            AirplaneModeInteractor(
+                airplaneModeRepository,
+                connectivityRepository,
+            ),
+            logger,
+            scope,
+        )
         viewModel = WifiViewModel(
+            airplaneModeViewModel,
             connectivityConstants,
             context,
             logger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index a3ad028..a1afcd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -22,11 +22,14 @@
 import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH
 import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+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.airplane.ui.viewmodel.AirplaneModeViewModel
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
@@ -64,19 +67,31 @@
     @Mock private lateinit var logger: ConnectivityPipelineLogger
     @Mock private lateinit var connectivityConstants: ConnectivityConstants
     @Mock private lateinit var wifiConstants: WifiConstants
+    private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
     private lateinit var connectivityRepository: FakeConnectivityRepository
     private lateinit var wifiRepository: FakeWifiRepository
     private lateinit var interactor: WifiInteractor
+    private lateinit var airplaneModeViewModel: AirplaneModeViewModel
     private lateinit var scope: CoroutineScope
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        airplaneModeRepository = FakeAirplaneModeRepository()
         connectivityRepository = FakeConnectivityRepository()
         wifiRepository = FakeWifiRepository()
         wifiRepository.setIsWifiEnabled(true)
         interactor = WifiInteractor(connectivityRepository, wifiRepository)
         scope = CoroutineScope(IMMEDIATE)
+        airplaneModeViewModel =
+            AirplaneModeViewModel(
+                AirplaneModeInteractor(
+                    airplaneModeRepository,
+                    connectivityRepository,
+                ),
+                logger,
+                scope,
+            )
     }
 
     @After
@@ -88,6 +103,7 @@
     fun wifiIcon() =
         runBlocking(IMMEDIATE) {
             wifiRepository.setIsWifiEnabled(testCase.enabled)
+            wifiRepository.setIsWifiDefault(testCase.isDefault)
             connectivityRepository.setForceHiddenIcons(
                 if (testCase.forceHidden) {
                     setOf(ConnectivitySlot.WIFI)
@@ -101,6 +117,7 @@
                 .thenReturn(testCase.hasDataCapabilities)
             underTest =
                 WifiViewModel(
+                    airplaneModeViewModel,
                     connectivityConstants,
                     context,
                     logger,
@@ -125,19 +142,12 @@
                 } else {
                     testCase.expected.contentDescription.invoke(context)
                 }
-            assertThat(iconFlow.value?.contentDescription?.getAsString())
+            assertThat(iconFlow.value?.contentDescription?.loadContentDescription(context))
                 .isEqualTo(expectedContentDescription)
 
             job.cancel()
         }
 
-    private fun ContentDescription.getAsString(): String? {
-        return when (this) {
-            is ContentDescription.Loaded -> this.description
-            is ContentDescription.Resource -> context.getString(this.res)
-        }
-    }
-
     internal data class Expected(
         /** The resource that should be used for the icon. */
         @DrawableRes val iconResource: Int,
@@ -159,6 +169,7 @@
         val forceHidden: Boolean = false,
         val alwaysShowIconWhenEnabled: Boolean = false,
         val hasDataCapabilities: Boolean = true,
+        val isDefault: Boolean = false,
         val network: WifiNetworkModel,
 
         /** The expected output. Null if we expect the output to be null. */
@@ -169,6 +180,7 @@
                 "forceHidden=$forceHidden, " +
                 "showWhenEnabled=$alwaysShowIconWhenEnabled, " +
                 "hasDataCaps=$hasDataCapabilities, " +
+                "isDefault=$isDefault, " +
                 "network=$network) then " +
                 "EXPECTED($expected)"
         }
@@ -303,6 +315,46 @@
                         ),
                 ),
 
+                // isDefault = true => all Inactive and Active networks shown
+                TestCase(
+                    isDefault = true,
+                    network = WifiNetworkModel.Inactive,
+                    expected =
+                        Expected(
+                            iconResource = WIFI_NO_NETWORK,
+                            contentDescription = { context ->
+                                "${context.getString(WIFI_NO_CONNECTION)}," +
+                                    context.getString(NO_INTERNET)
+                            },
+                            description = "No network icon",
+                        ),
+                ),
+                TestCase(
+                    isDefault = true,
+                    network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 3),
+                    expected =
+                        Expected(
+                            iconResource = WIFI_NO_INTERNET_ICONS[3],
+                            contentDescription = { context ->
+                                "${context.getString(WIFI_CONNECTION_STRENGTH[3])}," +
+                                    context.getString(NO_INTERNET)
+                            },
+                            description = "No internet level 3 icon",
+                        ),
+                ),
+                TestCase(
+                    isDefault = true,
+                    network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 1),
+                    expected =
+                        Expected(
+                            iconResource = WIFI_FULL_ICONS[1],
+                            contentDescription = { context ->
+                                context.getString(WIFI_CONNECTION_STRENGTH[1])
+                            },
+                            description = "Full internet level 1 icon",
+                        ),
+                ),
+
                 // network = CarrierMerged => not shown
                 TestCase(
                     network = WifiNetworkModel.CarrierMerged,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 3169eef..7d2c560 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -20,8 +20,12 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+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.airplane.ui.viewmodel.AirplaneModeViewModel
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
@@ -55,19 +59,31 @@
     @Mock private lateinit var logger: ConnectivityPipelineLogger
     @Mock private lateinit var connectivityConstants: ConnectivityConstants
     @Mock private lateinit var wifiConstants: WifiConstants
+    private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
     private lateinit var connectivityRepository: FakeConnectivityRepository
     private lateinit var wifiRepository: FakeWifiRepository
     private lateinit var interactor: WifiInteractor
+    private lateinit var airplaneModeViewModel: AirplaneModeViewModel
     private lateinit var scope: CoroutineScope
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        airplaneModeRepository = FakeAirplaneModeRepository()
         connectivityRepository = FakeConnectivityRepository()
         wifiRepository = FakeWifiRepository()
         wifiRepository.setIsWifiEnabled(true)
         interactor = WifiInteractor(connectivityRepository, wifiRepository)
         scope = CoroutineScope(IMMEDIATE)
+        airplaneModeViewModel = AirplaneModeViewModel(
+            AirplaneModeInteractor(
+                airplaneModeRepository,
+                connectivityRepository,
+            ),
+            logger,
+            scope,
+        )
+
         createAndSetViewModel()
     }
 
@@ -76,6 +92,8 @@
         scope.cancel()
     }
 
+    // See [WifiViewModelIconParameterizedTest] for additional view model tests.
+
     // Note on testing: [WifiViewModel] exposes 3 different instances of
     // [LocationBasedWifiViewModel]. In practice, these 3 different instances will get the exact
     // same data for icon, activity, etc. flows. So, most of these tests will test just one of the
@@ -460,11 +478,64 @@
         job.cancel()
     }
 
+    @Test
+    fun airplaneSpacer_notAirplaneMode_outputsFalse() = runBlocking(IMMEDIATE) {
+        var latest: Boolean? = null
+        val job = underTest
+            .qs
+            .isAirplaneSpacerVisible
+            .onEach { latest = it }
+            .launchIn(this)
+
+        airplaneModeRepository.setIsAirplaneMode(false)
+        yield()
+
+        assertThat(latest).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun airplaneSpacer_airplaneForceHidden_outputsFalse() = runBlocking(IMMEDIATE) {
+        var latest: Boolean? = null
+        val job = underTest
+            .qs
+            .isAirplaneSpacerVisible
+            .onEach { latest = it }
+            .launchIn(this)
+
+        airplaneModeRepository.setIsAirplaneMode(true)
+        connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.AIRPLANE))
+        yield()
+
+        assertThat(latest).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun airplaneSpacer_airplaneIconVisible_outputsTrue() = runBlocking(IMMEDIATE) {
+        var latest: Boolean? = null
+        val job = underTest
+            .qs
+            .isAirplaneSpacerVisible
+            .onEach { latest = it }
+            .launchIn(this)
+
+        airplaneModeRepository.setIsAirplaneMode(true)
+        yield()
+
+        assertThat(latest).isTrue()
+
+        job.cancel()
+    }
+
     private fun createAndSetViewModel() {
         // [WifiViewModel] creates its flows as soon as it's instantiated, and some of those flow
         // creations rely on certain config values that we mock out in individual tests. This method
         // allows tests to create the view model only after those configs are correctly set up.
         underTest = WifiViewModel(
+            airplaneModeViewModel,
             connectivityConstants,
             context,
             logger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 6ace404..915e999 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -23,8 +23,12 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
 import android.app.PendingIntent;
@@ -43,10 +47,14 @@
 import android.testing.TestableLooper;
 import android.view.ContentInfo;
 import android.view.View;
+import android.view.ViewRootImpl;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.widget.EditText;
 import android.widget.ImageButton;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
+import android.window.WindowOnBackInvokedDispatcher;
 
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
@@ -67,6 +75,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -229,6 +238,67 @@
     }
 
     @Test
+    public void testPredictiveBack_registerAndUnregister() throws Exception {
+        NotificationTestHelper helper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
+        ExpandableNotificationRow row = helper.createRow();
+        RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
+
+        ViewRootImpl viewRoot = mock(ViewRootImpl.class);
+        WindowOnBackInvokedDispatcher backInvokedDispatcher = mock(
+                WindowOnBackInvokedDispatcher.class);
+        ArgumentCaptor<OnBackInvokedCallback> onBackInvokedCallbackCaptor = ArgumentCaptor.forClass(
+                OnBackInvokedCallback.class);
+        when(viewRoot.getOnBackInvokedDispatcher()).thenReturn(backInvokedDispatcher);
+        view.setViewRootImpl(viewRoot);
+
+        /* verify that predictive back callback registered when RemoteInputView becomes visible */
+        view.onVisibilityAggregated(true);
+        verify(backInvokedDispatcher).registerOnBackInvokedCallback(
+                eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+                onBackInvokedCallbackCaptor.capture());
+
+        /* verify that same callback unregistered when RemoteInputView becomes invisible */
+        view.onVisibilityAggregated(false);
+        verify(backInvokedDispatcher).unregisterOnBackInvokedCallback(
+                eq(onBackInvokedCallbackCaptor.getValue()));
+    }
+
+    @Test
+    public void testUiPredictiveBack_openAndDispatchCallback() throws Exception {
+        NotificationTestHelper helper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
+        ExpandableNotificationRow row = helper.createRow();
+        RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
+        ViewRootImpl viewRoot = mock(ViewRootImpl.class);
+        WindowOnBackInvokedDispatcher backInvokedDispatcher = mock(
+                WindowOnBackInvokedDispatcher.class);
+        ArgumentCaptor<OnBackInvokedCallback> onBackInvokedCallbackCaptor = ArgumentCaptor.forClass(
+                OnBackInvokedCallback.class);
+        when(viewRoot.getOnBackInvokedDispatcher()).thenReturn(backInvokedDispatcher);
+        view.setViewRootImpl(viewRoot);
+        view.onVisibilityAggregated(true);
+        view.setEditTextReferenceToSelf();
+
+        /* capture the callback during registration */
+        verify(backInvokedDispatcher).registerOnBackInvokedCallback(
+                eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+                onBackInvokedCallbackCaptor.capture());
+
+        view.focus();
+
+        /* invoke the captured callback */
+        onBackInvokedCallbackCaptor.getValue().onBackInvoked();
+
+        /* verify that the RemoteInputView goes away */
+        assertEquals(view.getVisibility(), View.GONE);
+    }
+
+    @Test
     public void testUiEventLogging_openAndSend() throws Exception {
         NotificationTestHelper helper = new NotificationTestHelper(
                 mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index c4abedd..b68eb88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -63,8 +63,6 @@
     @Mock
     private lateinit var powerManager: PowerManager
 
-    private var shouldIgnoreViewRemoval: Boolean = false
-
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -209,26 +207,6 @@
         verify(windowManager, never()).removeView(any())
     }
 
-    @Test
-    fun removeView_shouldIgnoreRemovalFalse_viewRemoved() {
-        shouldIgnoreViewRemoval = false
-        underTest.displayView(getState())
-
-        underTest.removeView("reason")
-
-        verify(windowManager).removeView(any())
-    }
-
-    @Test
-    fun removeView_shouldIgnoreRemovalTrue_viewNotRemoved() {
-        shouldIgnoreViewRemoval = true
-        underTest.displayView(getState())
-
-        underTest.removeView("reason")
-
-        verify(windowManager, never()).removeView(any())
-    }
-
     private fun getState(name: String = "name") = ViewInfo(name)
 
     private fun getConfigurationListener(): ConfigurationListener {
@@ -253,7 +231,7 @@
         accessibilityManager,
         configurationController,
         powerManager,
-        R.layout.media_ttt_chip,
+        R.layout.chipbar,
         "Window Title",
         "WAKE_REASON",
     ) {
@@ -267,10 +245,6 @@
             mostRecentViewInfo = newInfo
         }
 
-        override fun shouldIgnoreViewRemoval(info: ViewInfo, removalReason: String): Boolean {
-            return shouldIgnoreViewRemoval
-        }
-
         override fun getTouchableRegion(view: View, outRect: Rect) {
             outRect.setEmpty()
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
index c9f2b4d..13e9f60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
@@ -19,9 +19,9 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogcatEchoTracker
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
new file mode 100644
index 0000000..9fbf159
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.temporarydisplay.chipbar
+
+import android.os.PowerManager
+import android.os.VibrationEffect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.view.ViewUtil
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ChipbarCoordinatorTest : SysuiTestCase() {
+    private lateinit var underTest: FakeChipbarCoordinator
+
+    @Mock private lateinit var logger: MediaTttLogger
+    @Mock private lateinit var accessibilityManager: AccessibilityManager
+    @Mock private lateinit var configurationController: ConfigurationController
+    @Mock private lateinit var powerManager: PowerManager
+    @Mock private lateinit var windowManager: WindowManager
+    @Mock private lateinit var falsingManager: FalsingManager
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var viewUtil: ViewUtil
+    @Mock private lateinit var vibratorHelper: VibratorHelper
+    private lateinit var fakeClock: FakeSystemClock
+    private lateinit var fakeExecutor: FakeExecutor
+    private lateinit var uiEventLoggerFake: UiEventLoggerFake
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
+
+        fakeClock = FakeSystemClock()
+        fakeExecutor = FakeExecutor(fakeClock)
+
+        uiEventLoggerFake = UiEventLoggerFake()
+
+        underTest =
+            FakeChipbarCoordinator(
+                context,
+                logger,
+                windowManager,
+                fakeExecutor,
+                accessibilityManager,
+                configurationController,
+                powerManager,
+                falsingManager,
+                falsingCollector,
+                viewUtil,
+                vibratorHelper,
+            )
+        underTest.start()
+    }
+
+    @Test
+    fun displayView_loadedIcon_correctlyRendered() {
+        val drawable = context.getDrawable(R.drawable.ic_celebration)!!
+
+        underTest.displayView(
+            ChipbarInfo(
+                Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
+                Text.Loaded("text"),
+                endItem = null,
+            )
+        )
+
+        val iconView = getChipbarView().getStartIconView()
+        assertThat(iconView.drawable).isEqualTo(drawable)
+        assertThat(iconView.contentDescription).isEqualTo("loadedCD")
+    }
+
+    @Test
+    fun displayView_resourceIcon_correctlyRendered() {
+        val contentDescription = ContentDescription.Resource(R.string.controls_error_timeout)
+        underTest.displayView(
+            ChipbarInfo(
+                Icon.Resource(R.drawable.ic_cake, contentDescription),
+                Text.Loaded("text"),
+                endItem = null,
+            )
+        )
+
+        val iconView = getChipbarView().getStartIconView()
+        assertThat(iconView.contentDescription)
+            .isEqualTo(contentDescription.loadContentDescription(context))
+    }
+
+    @Test
+    fun displayView_loadedText_correctlyRendered() {
+        underTest.displayView(
+            ChipbarInfo(
+                Icon.Resource(R.id.check_box, null),
+                Text.Loaded("display view text here"),
+                endItem = null,
+            )
+        )
+
+        assertThat(getChipbarView().getChipText()).isEqualTo("display view text here")
+    }
+
+    @Test
+    fun displayView_resourceText_correctlyRendered() {
+        underTest.displayView(
+            ChipbarInfo(
+                Icon.Resource(R.id.check_box, null),
+                Text.Resource(R.string.screenrecord_start_error),
+                endItem = null,
+            )
+        )
+
+        assertThat(getChipbarView().getChipText())
+            .isEqualTo(context.getString(R.string.screenrecord_start_error))
+    }
+
+    @Test
+    fun displayView_endItemNull_correctlyRendered() {
+        underTest.displayView(
+            ChipbarInfo(
+                Icon.Resource(R.id.check_box, null),
+                Text.Loaded("text"),
+                endItem = null,
+            )
+        )
+
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE)
+    }
+
+    @Test
+    fun displayView_endItemLoading_correctlyRendered() {
+        underTest.displayView(
+            ChipbarInfo(
+                Icon.Resource(R.id.check_box, null),
+                Text.Loaded("text"),
+                endItem = ChipbarEndItem.Loading,
+            )
+        )
+
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
+        assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE)
+    }
+
+    @Test
+    fun displayView_endItemError_correctlyRendered() {
+        underTest.displayView(
+            ChipbarInfo(
+                Icon.Resource(R.id.check_box, null),
+                Text.Loaded("text"),
+                endItem = ChipbarEndItem.Error,
+            )
+        )
+
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
+        assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE)
+    }
+
+    @Test
+    fun displayView_endItemButton_correctlyRendered() {
+        underTest.displayView(
+            ChipbarInfo(
+                Icon.Resource(R.id.check_box, null),
+                Text.Loaded("text"),
+                endItem =
+                    ChipbarEndItem.Button(
+                        Text.Loaded("button text"),
+                        onClickListener = {},
+                    ),
+            )
+        )
+
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.VISIBLE)
+        assertThat(chipbarView.getEndButton().text).isEqualTo("button text")
+        assertThat(chipbarView.getEndButton().hasOnClickListeners()).isTrue()
+    }
+
+    @Test
+    fun displayView_endItemButtonClicked_falseTap_listenerNotRun() {
+        whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true)
+        var isClicked = false
+        val buttonClickListener = View.OnClickListener { isClicked = true }
+
+        underTest.displayView(
+            ChipbarInfo(
+                Icon.Resource(R.id.check_box, null),
+                Text.Loaded("text"),
+                endItem =
+                    ChipbarEndItem.Button(
+                        Text.Loaded("button text"),
+                        buttonClickListener,
+                    ),
+            )
+        )
+
+        getChipbarView().getEndButton().performClick()
+
+        assertThat(isClicked).isFalse()
+    }
+
+    @Test
+    fun displayView_endItemButtonClicked_notFalseTap_listenerRun() {
+        whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false)
+        var isClicked = false
+        val buttonClickListener = View.OnClickListener { isClicked = true }
+
+        underTest.displayView(
+            ChipbarInfo(
+                Icon.Resource(R.id.check_box, null),
+                Text.Loaded("text"),
+                endItem =
+                    ChipbarEndItem.Button(
+                        Text.Loaded("button text"),
+                        buttonClickListener,
+                    ),
+            )
+        )
+
+        getChipbarView().getEndButton().performClick()
+
+        assertThat(isClicked).isTrue()
+    }
+
+    @Test
+    fun displayView_vibrationEffect_doubleClickEffect() {
+        underTest.displayView(
+            ChipbarInfo(
+                Icon.Resource(R.id.check_box, null),
+                Text.Loaded("text"),
+                endItem = null,
+                vibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK),
+            )
+        )
+
+        verify(vibratorHelper).vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK))
+    }
+
+    @Test
+    fun updateView_viewUpdated() {
+        // First, display a view
+        val drawable = context.getDrawable(R.drawable.ic_celebration)!!
+
+        underTest.displayView(
+            ChipbarInfo(
+                Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
+                Text.Loaded("title text"),
+                endItem = ChipbarEndItem.Loading,
+            )
+        )
+
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getStartIconView().drawable).isEqualTo(drawable)
+        assertThat(chipbarView.getStartIconView().contentDescription).isEqualTo("loadedCD")
+        assertThat(chipbarView.getChipText()).isEqualTo("title text")
+        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
+        assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE)
+
+        // WHEN the view is updated
+        val newDrawable = context.getDrawable(R.drawable.ic_cake)!!
+        underTest.updateView(
+            ChipbarInfo(
+                Icon.Loaded(newDrawable, ContentDescription.Loaded("new CD")),
+                Text.Loaded("new title text"),
+                endItem = ChipbarEndItem.Error,
+            ),
+            chipbarView
+        )
+
+        // THEN we display the new view
+        assertThat(chipbarView.getStartIconView().drawable).isEqualTo(newDrawable)
+        assertThat(chipbarView.getStartIconView().contentDescription).isEqualTo("new CD")
+        assertThat(chipbarView.getChipText()).isEqualTo("new title text")
+        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
+        assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE)
+    }
+
+    private fun ViewGroup.getStartIconView() = this.requireViewById<ImageView>(R.id.start_icon)
+
+    private fun ViewGroup.getChipText(): String =
+        (this.requireViewById<TextView>(R.id.text)).text as String
+
+    private fun ViewGroup.getLoadingIcon(): View = this.requireViewById(R.id.loading)
+
+    private fun ViewGroup.getEndButton(): TextView = this.requireViewById(R.id.end_button)
+
+    private fun ViewGroup.getErrorIcon(): View = this.requireViewById(R.id.error)
+
+    private fun getChipbarView(): ViewGroup {
+        val viewCaptor = ArgumentCaptor.forClass(View::class.java)
+        verify(windowManager).addView(viewCaptor.capture(), any())
+        return viewCaptor.value as ViewGroup
+    }
+}
+
+private const val TIMEOUT = 10000
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
new file mode 100644
index 0000000..17d4023
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.temporarydisplay.chipbar
+
+import android.content.Context
+import android.os.PowerManager
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.view.ViewUtil
+
+/** A fake implementation of [ChipbarCoordinator] for testing. */
+class FakeChipbarCoordinator(
+    context: Context,
+    @MediaTttReceiverLogger logger: MediaTttLogger,
+    windowManager: WindowManager,
+    mainExecutor: DelayableExecutor,
+    accessibilityManager: AccessibilityManager,
+    configurationController: ConfigurationController,
+    powerManager: PowerManager,
+    falsingManager: FalsingManager,
+    falsingCollector: FalsingCollector,
+    viewUtil: ViewUtil,
+    vibratorHelper: VibratorHelper,
+) :
+    ChipbarCoordinator(
+        context,
+        logger,
+        windowManager,
+        mainExecutor,
+        accessibilityManager,
+        configurationController,
+        powerManager,
+        falsingManager,
+        falsingCollector,
+        viewUtil,
+        vibratorHelper,
+    ) {
+    override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+        // Just bypass the animation in tests
+        onAnimationEnd.run()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
index d951f36..525d837 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
@@ -110,7 +110,7 @@
         val thirdExpectedValue =
             setUpUsers(
                 count = 2,
-                hasGuest = true,
+                isLastGuestUser = true,
                 selectedIndex = 1,
             )
         underTest.refreshUsers()
@@ -121,21 +121,25 @@
     }
 
     @Test
-    fun `refreshUsers - sorts by creation time`() = runSelfCancelingTest {
+    fun `refreshUsers - sorts by creation time - guest user last`() = runSelfCancelingTest {
         underTest = create(this)
         val unsortedUsers =
             setUpUsers(
                 count = 3,
                 selectedIndex = 0,
+                isLastGuestUser = true,
             )
-        unsortedUsers[0].creationTime = 900
-        unsortedUsers[1].creationTime = 700
-        unsortedUsers[2].creationTime = 999
-        val expectedUsers = listOf(unsortedUsers[1], unsortedUsers[0], unsortedUsers[2])
+        unsortedUsers[0].creationTime = 999
+        unsortedUsers[1].creationTime = 900
+        unsortedUsers[2].creationTime = 950
+        val expectedUsers =
+            listOf(
+                unsortedUsers[1],
+                unsortedUsers[0],
+                unsortedUsers[2], // last because this is the guest
+            )
         var userInfos: List<UserInfo>? = null
-        var selectedUserInfo: UserInfo? = null
         underTest.userInfos.onEach { userInfos = it }.launchIn(this)
-        underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
 
         underTest.refreshUsers()
         assertThat(userInfos).isEqualTo(expectedUsers)
@@ -143,14 +147,14 @@
 
     private fun setUpUsers(
         count: Int,
-        hasGuest: Boolean = false,
+        isLastGuestUser: Boolean = false,
         selectedIndex: Int = 0,
     ): List<UserInfo> {
         val userInfos =
             (0 until count).map { index ->
                 createUserInfo(
                     index,
-                    isGuest = hasGuest && index == count - 1,
+                    isGuest = isLastGuestUser && index == count - 1,
                 )
             }
         whenever(manager.aliveUsers).thenReturn(userInfos)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
index d4b41c1..a363a03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
@@ -97,6 +97,7 @@
                         createUserRecord(2),
                         createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
                         createActionRecord(UserActionModel.ENTER_GUEST_MODE),
+                        createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT),
                     )
                 )
             var models: List<UserModel>? = null
@@ -176,15 +177,17 @@
                         createUserRecord(2),
                         createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
                         createActionRecord(UserActionModel.ENTER_GUEST_MODE),
+                        createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT),
                     )
                 )
             var models: List<UserActionModel>? = null
             val job = underTest.actions.onEach { models = it }.launchIn(this)
 
-            assertThat(models).hasSize(3)
+            assertThat(models).hasSize(4)
             assertThat(models?.get(0)).isEqualTo(UserActionModel.ADD_USER)
             assertThat(models?.get(1)).isEqualTo(UserActionModel.ADD_SUPERVISED_USER)
             assertThat(models?.get(2)).isEqualTo(UserActionModel.ENTER_GUEST_MODE)
+            assertThat(models?.get(3)).isEqualTo(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
             job.cancel()
         }
 
@@ -200,6 +203,7 @@
             isAddUser = action == UserActionModel.ADD_USER,
             isAddSupervisedUser = action == UserActionModel.ADD_SUPERVISED_USER,
             isGuest = action == UserActionModel.ENTER_GUEST_MODE,
+            isManageUsers = action == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
         )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
index 1540f85..97571b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
@@ -28,6 +28,7 @@
 import com.android.internal.R.drawable.ic_account_circle
 import com.android.systemui.R
 import com.android.systemui.common.shared.model.Text
+import com.android.systemui.qs.user.UserSwitchDialogController
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.user.data.source.UserRecord
 import com.android.systemui.user.domain.model.ShowDialogRequestModel
@@ -316,14 +317,16 @@
             keyguardRepository.setKeyguardShowing(false)
             var dialogRequest: ShowDialogRequestModel? = null
             val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+            val dialogShower: UserSwitchDialogController.DialogShower = mock()
 
-            underTest.executeAction(UserActionModel.ADD_USER)
+            underTest.executeAction(UserActionModel.ADD_USER, dialogShower)
             assertThat(dialogRequest)
                 .isEqualTo(
                     ShowDialogRequestModel.ShowAddUserDialog(
                         userHandle = userInfos[0].userHandle,
                         isKeyguardShowing = false,
                         showEphemeralMessage = false,
+                        dialogShower = dialogShower,
                     )
                 )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
index c3a9705..6a17c8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
@@ -64,13 +64,7 @@
     @Test
     fun `actions - not actionable when locked and not locked`() =
         runBlocking(IMMEDIATE) {
-            userRepository.setActions(
-                listOf(
-                    UserActionModel.ENTER_GUEST_MODE,
-                    UserActionModel.ADD_USER,
-                    UserActionModel.ADD_SUPERVISED_USER,
-                )
-            )
+            setActions()
             userRepository.setActionableWhenLocked(false)
             keyguardRepository.setKeyguardShowing(false)
 
@@ -92,13 +86,7 @@
     @Test
     fun `actions - actionable when locked and not locked`() =
         runBlocking(IMMEDIATE) {
-            userRepository.setActions(
-                listOf(
-                    UserActionModel.ENTER_GUEST_MODE,
-                    UserActionModel.ADD_USER,
-                    UserActionModel.ADD_SUPERVISED_USER,
-                )
-            )
+            setActions()
             userRepository.setActionableWhenLocked(true)
             keyguardRepository.setKeyguardShowing(false)
 
@@ -120,13 +108,7 @@
     @Test
     fun `actions - actionable when locked and locked`() =
         runBlocking(IMMEDIATE) {
-            userRepository.setActions(
-                listOf(
-                    UserActionModel.ENTER_GUEST_MODE,
-                    UserActionModel.ADD_USER,
-                    UserActionModel.ADD_SUPERVISED_USER,
-                )
-            )
+            setActions()
             userRepository.setActionableWhenLocked(true)
             keyguardRepository.setKeyguardShowing(true)
 
@@ -182,6 +164,10 @@
         verify(activityStarter).startActivity(any(), anyBoolean())
     }
 
+    private fun setActions() {
+        userRepository.setActions(UserActionModel.values().toList())
+    }
+
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index 0344e3f..c12a868 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -268,6 +268,26 @@
         }
 
     @Test
+    fun `menu actions`() =
+        runBlocking(IMMEDIATE) {
+            userRepository.setActions(UserActionModel.values().toList())
+            var actions: List<UserActionViewModel>? = null
+            val job = underTest.menu.onEach { actions = it }.launchIn(this)
+
+            assertThat(actions?.map { it.viewKey })
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(),
+                        UserActionModel.ADD_USER.ordinal.toLong(),
+                        UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(),
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(),
+                    )
+                )
+
+            job.cancel()
+        }
+
+    @Test
     fun `isFinishRequested - finishes when user is switched`() =
         runBlocking(IMMEDIATE) {
             setUsers(count = 2)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt
deleted file mode 100644
index 5e09b81..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * 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.util.collection
-
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertSame
-import org.junit.Assert.assertThrows
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class RingBufferTest : SysuiTestCase() {
-
-    private val buffer = RingBuffer(5) { TestElement() }
-
-    private val history = mutableListOf<TestElement>()
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-    }
-
-    @Test
-    fun testBarelyFillBuffer() {
-        fillBuffer(5)
-
-        assertEquals(0, buffer[0].id)
-        assertEquals(1, buffer[1].id)
-        assertEquals(2, buffer[2].id)
-        assertEquals(3, buffer[3].id)
-        assertEquals(4, buffer[4].id)
-    }
-
-    @Test
-    fun testPartiallyFillBuffer() {
-        fillBuffer(3)
-
-        assertEquals(3, buffer.size)
-
-        assertEquals(0, buffer[0].id)
-        assertEquals(1, buffer[1].id)
-        assertEquals(2, buffer[2].id)
-
-        assertThrows(IndexOutOfBoundsException::class.java) { buffer[3] }
-        assertThrows(IndexOutOfBoundsException::class.java) { buffer[4] }
-    }
-
-    @Test
-    fun testSpinBuffer() {
-        fillBuffer(277)
-
-        assertEquals(272, buffer[0].id)
-        assertEquals(273, buffer[1].id)
-        assertEquals(274, buffer[2].id)
-        assertEquals(275, buffer[3].id)
-        assertEquals(276, buffer[4].id)
-        assertThrows(IndexOutOfBoundsException::class.java) { buffer[5] }
-
-        assertEquals(5, buffer.size)
-    }
-
-    @Test
-    fun testElementsAreRecycled() {
-        fillBuffer(23)
-
-        assertSame(history[4], buffer[1])
-        assertSame(history[9], buffer[1])
-        assertSame(history[14], buffer[1])
-        assertSame(history[19], buffer[1])
-    }
-
-    @Test
-    fun testIterator() {
-        fillBuffer(13)
-
-        val iterator = buffer.iterator()
-
-        for (i in 0 until 5) {
-            assertEquals(history[8 + i], iterator.next())
-        }
-        assertFalse(iterator.hasNext())
-        assertThrows(NoSuchElementException::class.java) { iterator.next() }
-    }
-
-    @Test
-    fun testForEach() {
-        fillBuffer(13)
-        var i = 8
-
-        buffer.forEach {
-            assertEquals(history[i], it)
-            i++
-        }
-        assertEquals(13, i)
-    }
-
-    private fun fillBuffer(count: Int) {
-        for (i in 0 until count) {
-            val elem = buffer.advance()
-            elem.id = history.size
-            history.add(elem)
-        }
-    }
-}
-
-private class TestElement(var id: Int = 0) {
-    override fun toString(): String {
-        return "{TestElement $id}"
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 09da52e..fa7ebf6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -80,6 +80,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.colorextraction.ColorExtractor;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
@@ -91,6 +92,7 @@
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.RankingBuilder;
@@ -190,6 +192,8 @@
     private NotificationShadeWindowView mNotificationShadeWindowView;
     @Mock
     private AuthController mAuthController;
+    @Mock
+    private ShadeExpansionStateManager mShadeExpansionStateManager;
 
     private SysUiState mSysUiState;
     private boolean mSysUiStateBubblesExpanded;
@@ -290,7 +294,7 @@
                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
                 mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
                 mColorExtractor, mDumpManager, mKeyguardStateController,
-                mScreenOffAnimationController, mAuthController);
+                mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager);
         mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
         mNotificationShadeWindowController.attach();
 
@@ -343,7 +347,8 @@
                         mock(NotificationInterruptLogger.class),
                         mock(Handler.class),
                         mock(NotifPipelineFlags.class),
-                        mock(KeyguardNotificationVisibilityProvider.class)
+                        mock(KeyguardNotificationVisibilityProvider.class),
+                        mock(UiEventLogger.class)
                 );
         when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor);
         mBubbleController = new TestableBubbleController(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
index 9635faf..e5316bc8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
@@ -22,6 +22,7 @@
 import android.os.PowerManager;
 import android.service.dreams.IDreamManager;
 
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
@@ -46,7 +47,8 @@
             NotificationInterruptLogger logger,
             Handler mainHandler,
             NotifPipelineFlags flags,
-            KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider) {
+            KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
+            UiEventLogger uiEventLogger) {
         super(contentResolver,
                 powerManager,
                 dreamManager,
@@ -58,7 +60,8 @@
                 logger,
                 mainHandler,
                 flags,
-                keyguardNotificationVisibilityProvider);
+                keyguardNotificationVisibilityProvider,
+                uiEventLogger);
         mUseHeadsUp = true;
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index cebe946..7af66f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -34,6 +34,8 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.floating.FloatingTasks;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedEventCallback;
@@ -49,6 +51,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.Optional;
+import java.util.concurrent.Executor;
 
 /**
  * Tests for {@link WMShell}.
@@ -76,12 +79,14 @@
     @Mock UserTracker mUserTracker;
     @Mock ShellExecutor mSysUiMainExecutor;
     @Mock FloatingTasks mFloatingTasks;
+    @Mock DesktopMode mDesktopMode;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mWMShell = new WMShell(mContext, mShellInterface, Optional.of(mPip),
                 Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mFloatingTasks),
+                Optional.of(mDesktopMode),
                 mCommandQueue, mConfigurationController, mKeyguardStateController,
                 mKeyguardUpdateMonitor, mScreenLifecycle, mSysUiState, mProtoTracer,
                 mWakefulnessLifecycle, mUserTracker, mSysUiMainExecutor);
@@ -103,4 +108,12 @@
         verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class));
         verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback.class));
     }
+
+    @Test
+    public void initDesktopMode_registersListener() {
+        mWMShell.initDesktopMode(mDesktopMode);
+        verify(mDesktopMode).addListener(
+                any(DesktopModeTaskRepository.VisibleTasksListener.class),
+                any(Executor.class));
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
index 5d52be2..a60b773 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -26,7 +26,7 @@
     private val listenerFlagIds = mutableMapOf<FlagListenable.Listener, MutableSet<Int>>()
 
     init {
-        Flags.getFlagFields().forEach { field ->
+        Flags.flagFields.forEach { field ->
             val flag: Flag<*> = field.get(null) as Flag<*>
             knownFlagNames[flag.id] = field.name
         }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 725b1f4..0c12680 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.data.repository
 
 import com.android.systemui.common.shared.model.Position
+import com.android.systemui.keyguard.shared.model.StatusBarState
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -44,6 +45,9 @@
     private val _dozeAmount = MutableStateFlow(0f)
     override val dozeAmount: Flow<Float> = _dozeAmount
 
+    private val _statusBarState = MutableStateFlow(StatusBarState.SHADE)
+    override val statusBarState: Flow<StatusBarState> = _statusBarState
+
     override fun isKeyguardShowing(): Boolean {
         return _isKeyguardShowing.value
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
index 5272585..c33ce5d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.qs
 
-import android.view.View
+import com.android.systemui.animation.Expandable
 import com.android.systemui.qs.FgsManagerController.OnDialogDismissedListener
 import com.android.systemui.qs.FgsManagerController.OnNumberOfPackagesChangedListener
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -54,7 +54,7 @@
 
     override fun init() {}
 
-    override fun showDialog(viewLaunchedFrom: View?) {}
+    override fun showDialog(expandable: Expandable?) {}
 
     override fun addOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener) {
         numRunningPackagesListeners.add(listener)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
index 2a9aedd..325da4e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
@@ -57,7 +57,6 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.settings.GlobalSettings
-import com.android.systemui.util.time.FakeSystemClock
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.test.TestCoroutineDispatcher
 
@@ -68,7 +67,6 @@
 class FooterActionsTestUtils(
     private val context: Context,
     private val testableLooper: TestableLooper,
-    private val fakeClock: FakeSystemClock = FakeSystemClock(),
 ) {
     /** Enable or disable the user switcher in the settings. */
     fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean, userId: Int) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 6417db0..29194c5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -657,25 +657,27 @@
                     userState.mBindingServices.removeIf(filter);
                     userState.mCrashedServices.removeIf(filter);
                     final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
+                    boolean anyServiceRemoved = false;
                     while (it.hasNext()) {
                         final ComponentName comp = it.next();
                         final String compPkg = comp.getPackageName();
                         if (compPkg.equals(packageName)) {
                             it.remove();
-                            // Update the enabled services setting.
-                            persistComponentNamesToSettingLocked(
-                                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
-                                    userState.mEnabledServices, userId);
-                            // Update the touch exploration granted services setting.
                             userState.mTouchExplorationGrantedServices.remove(comp);
-                            persistComponentNamesToSettingLocked(
-                                    Settings.Secure.
-                                    TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
-                                    userState.mTouchExplorationGrantedServices, userId);
-                            onUserStateChangedLocked(userState);
-                            return;
+                            anyServiceRemoved = true;
                         }
                     }
+                    if (anyServiceRemoved) {
+                        // Update the enabled services setting.
+                        persistComponentNamesToSettingLocked(
+                                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                                userState.mEnabledServices, userId);
+                        // Update the touch exploration granted services setting.
+                        persistComponentNamesToSettingLocked(
+                                Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
+                                userState.mTouchExplorationGrantedServices, userId);
+                        onUserStateChangedLocked(userState);
+                    }
                 }
             }
 
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 38237fa..23cacf3 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -866,6 +866,33 @@
     }
 
     @Override
+    public void setAppWidgetHidden(String callingPackage, int hostId) {
+        final int userId = UserHandle.getCallingUserId();
+
+        if (DEBUG) {
+            Slog.i(TAG, "setAppWidgetHidden() " + userId);
+        }
+
+        mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+        synchronized (mLock) {
+            ensureGroupStateLoadedLocked(userId, /* enforceUserUnlockingOrUnlocked */false);
+
+            HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);
+            Host host = lookupHostLocked(id);
+
+            if (host != null) {
+                try {
+                    mAppOpsManagerInternal.updateAppWidgetVisibility(host.getWidgetUids(), false);
+                } catch (NullPointerException e) {
+                    Slog.e(TAG, "setAppWidgetHidden(): Getting host uids: " + host.toString(), e);
+                    throw e;
+                }
+            }
+        }
+    }
+
+    @Override
     public void deleteAppWidgetId(String callingPackage, int appWidgetId) {
         final int userId = UserHandle.getCallingUserId();
 
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 8d878af..fcc1ef2a 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -152,6 +152,8 @@
 
     // flag set by resource, whether to start dream immediately upon docking even if unlocked.
     private boolean mStartDreamImmediatelyOnDock = true;
+    // flag set by resource, whether to disable dreams when ambient mode suppression is enabled.
+    private boolean mDreamsDisabledByAmbientModeSuppression = false;
     // flag set by resource, whether to enable Car dock launch when starting car mode.
     private boolean mEnableCarDockLaunch = true;
     // flag set by resource, whether to lock UI mode to the default one or not.
@@ -364,6 +366,11 @@
         mStartDreamImmediatelyOnDock = startDreamImmediatelyOnDock;
     }
 
+    @VisibleForTesting
+    void setDreamsDisabledByAmbientModeSuppression(boolean disabledByAmbientModeSuppression) {
+        mDreamsDisabledByAmbientModeSuppression = disabledByAmbientModeSuppression;
+    }
+
     @Override
     public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
         mCurrentUser = to.getUserIdentifier();
@@ -424,6 +431,8 @@
         final Resources res = context.getResources();
         mStartDreamImmediatelyOnDock = res.getBoolean(
                 com.android.internal.R.bool.config_startDreamImmediatelyOnDock);
+        mDreamsDisabledByAmbientModeSuppression = res.getBoolean(
+                com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
         mNightMode = res.getInteger(
                 com.android.internal.R.integer.config_defaultNightMode);
         mDefaultUiModeType = res.getInteger(
@@ -1827,10 +1836,14 @@
         // Send the new configuration.
         applyConfigurationExternallyLocked();
 
+        final boolean dreamsSuppressed = mDreamsDisabledByAmbientModeSuppression
+                && mLocalPowerManager.isAmbientDisplaySuppressed();
+
         // If we did not start a dock app, then start dreaming if appropriate.
-        if (category != null && !dockAppStarted && (mStartDreamImmediatelyOnDock
-                || mWindowManager.isKeyguardShowingAndNotOccluded()
-                || !mPowerManager.isInteractive())) {
+        if (category != null && !dockAppStarted && !dreamsSuppressed && (
+                mStartDreamImmediatelyOnDock
+                        || mWindowManager.isKeyguardShowingAndNotOccluded()
+                        || !mPowerManager.isInteractive())) {
             mInjector.startDreamWhenDockedIfAppropriate(getContext());
         }
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index fa75100..a778b57 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -16,6 +16,7 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
 
 import android.annotation.NonNull;
@@ -57,6 +58,7 @@
 import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
 
+import java.time.Clock;
 import java.util.ArrayList;
 import java.util.function.Supplier;
 
@@ -88,7 +90,9 @@
     private long mWaitForAuthKeyguard;
     private long mWaitForAuthBp;
     private long mIgnoreAuthFor;
+    private long mSideFpsLastAcquireStartTime;
     private Runnable mAuthSuccessRunnable;
+    private final Clock mClock;
 
     FingerprintAuthenticationClient(
             @NonNull Context context,
@@ -112,7 +116,8 @@
             @Nullable ISidefpsController sidefpsController,
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
-            @NonNull Handler handler) {
+            @NonNull Handler handler,
+            @NonNull Clock clock) {
         super(
                 context,
                 lazyDaemon,
@@ -154,6 +159,8 @@
         mSkipWaitForPowerVendorAcquireMessage =
                 context.getResources().getInteger(
                         R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage);
+        mSideFpsLastAcquireStartTime = -1;
+        mClock = clock;
 
         if (mSensorProps.isAnySidefpsType()) {
             if (Build.isDebuggable()) {
@@ -235,8 +242,14 @@
                             return;
                         }
                         delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp;
-                        Slog.i(TAG, "(sideFPS) Auth succeeded, sideFps waiting for power for: "
-                                + delay + "ms");
+
+                        if (mSideFpsLastAcquireStartTime != -1) {
+                            delay = Math.max(0,
+                                    delay - (mClock.millis() - mSideFpsLastAcquireStartTime));
+                        }
+
+                        Slog.i(TAG, "(sideFPS) Auth succeeded, sideFps "
+                                + "waiting for power until: " + delay + "ms");
                     }
 
                     if (mHandler.hasMessages(MESSAGE_FINGER_UP)) {
@@ -260,13 +273,15 @@
         mSensorOverlays.ifUdfps(controller -> controller.onAcquired(getSensorId(), acquiredInfo));
         super.onAcquired(acquiredInfo, vendorCode);
         if (mSensorProps.isAnySidefpsType()) {
+            if (acquiredInfo == FINGERPRINT_ACQUIRED_START) {
+                mSideFpsLastAcquireStartTime = mClock.millis();
+            }
             final boolean shouldLookForVendor =
                     mSkipWaitForPowerAcquireMessage == FINGERPRINT_ACQUIRED_VENDOR;
             final boolean acquireMessageMatch = acquiredInfo == mSkipWaitForPowerAcquireMessage;
             final boolean vendorMessageMatch = vendorCode == mSkipWaitForPowerVendorAcquireMessage;
             final boolean ignorePowerPress =
-                    (acquireMessageMatch && !shouldLookForVendor) || (shouldLookForVendor
-                            && acquireMessageMatch && vendorMessageMatch);
+                    acquireMessageMatch && (!shouldLookForVendor || vendorMessageMatch);
 
             if (ignorePowerPress) {
                 Slog.d(TAG, "(sideFPS) onFingerUp");
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 6f6c09b..dabb2cf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -47,6 +47,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemClock;
 import android.os.UserManager;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -447,7 +448,8 @@
                     mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
                     mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication,
-                    mSensors.get(sensorId).getSensorProperties(), mHandler);
+                    mSensors.get(sensorId).getSensorProperties(), mHandler,
+                    SystemClock.elapsedRealtimeClock());
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
         });
     }
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 17215e5..8f59ffd 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -220,6 +220,11 @@
     }
 
     private void backgroundStart(float initialBrightness) {
+        synchronized (mDataCollectionLock) {
+            if (mStarted) {
+                return;
+            }
+        }
         if (DEBUG) {
             Slog.d(TAG, "Background start");
         }
@@ -250,6 +255,11 @@
 
     /** Stop listening for events */
     void stop() {
+        synchronized (mDataCollectionLock) {
+            if (!mStarted) {
+                return;
+            }
+        }
         if (DEBUG) {
             Slog.d(TAG, "Stop");
         }
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 1ae37bb..687d03d 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -150,7 +150,7 @@
  *       <quirk>canSetBrightnessViaHwc</quirk>
  *      </quirks>
  *
- *      <autoBrightness>
+ *      <autoBrightness enable="true">
  *          <brighteningLightDebounceMillis>
  *              2000
  *          </brighteningLightDebounceMillis>
@@ -507,6 +507,11 @@
     private long mAutoBrightnessDarkeningLightDebounce =
             INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE;
 
+    // This setting allows non-default displays to have autobrightness enabled.
+    private boolean mAutoBrightnessAvailable = false;
+    // This stores the raw value loaded from the config file - true if not written.
+    private boolean mDdcAutoBrightnessAvailable = true;
+
     // Brightness Throttling data may be updated via the DeviceConfig. Here we store the original
     // data, which comes from the ddc, and the current one, which may be the DeviceConfig
     // overwritten value.
@@ -1119,6 +1124,10 @@
         return mProximitySensor;
     }
 
+    boolean isAutoBrightnessAvailable() {
+        return mAutoBrightnessAvailable;
+    }
+
     /**
      * @param quirkValue The quirk to test.
      * @return {@code true} if the specified quirk is present in this configuration, {@code false}
@@ -1184,6 +1193,60 @@
         return mBrightnessLevelsNits;
     }
 
+    /**
+     * @return Default peak refresh rate of the associated display
+     */
+    public int getDefaultPeakRefreshRate() {
+        return mContext.getResources().getInteger(R.integer.config_defaultPeakRefreshRate);
+    }
+
+    /**
+     * @return Default refresh rate of the associated display
+     */
+    public int getDefaultRefreshRate() {
+        return mContext.getResources().getInteger(R.integer.config_defaultRefreshRate);
+    }
+
+    /**
+     * @return An array of lower display brightness thresholds. This, in combination with lower
+     * ambient brightness thresholds help define buckets in which the refresh rate switching is not
+     * allowed
+     */
+    public int[] getLowDisplayBrightnessThresholds() {
+        return mContext.getResources().getIntArray(
+                R.array.config_brightnessThresholdsOfPeakRefreshRate);
+    }
+
+    /**
+     * @return An array of lower ambient brightness thresholds. This, in combination with lower
+     * display brightness thresholds help define buckets in which the refresh rate switching is not
+     * allowed
+     */
+    public int[] getLowAmbientBrightnessThresholds() {
+        return mContext.getResources().getIntArray(
+                R.array.config_ambientThresholdsOfPeakRefreshRate);
+    }
+
+    /**
+     * @return An array of high display brightness thresholds. This, in combination with high
+     * ambient brightness thresholds help define buckets in which the refresh rate switching is not
+     * allowed
+     */
+    public int[] getHighDisplayBrightnessThresholds() {
+        return mContext.getResources().getIntArray(
+                R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate);
+    }
+
+    /**
+     * @return An array of high ambient brightness thresholds. This, in combination with high
+     * display brightness thresholds help define buckets in which the refresh rate switching is not
+     * allowed
+     */
+    public int[] getHighAmbientBrightnessThresholds() {
+        return mContext.getResources().getIntArray(
+                R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate);
+    }
+
     @Override
     public String toString() {
         return "DisplayDeviceConfig{"
@@ -1271,6 +1334,8 @@
                 + mAutoBrightnessDarkeningLightDebounce
                 + ", mBrightnessLevelsLux= " + Arrays.toString(mBrightnessLevelsLux)
                 + ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits)
+                + ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
+                + ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
                 + "}";
     }
 
@@ -1349,6 +1414,7 @@
         loadBrightnessChangeThresholdsFromXml();
         setProxSensorUnspecified();
         loadAutoBrightnessConfigsFromConfigXml();
+        loadAutoBrightnessAvailableFromConfigXml();
         mLoadedFrom = "<config.xml>";
     }
 
@@ -1367,6 +1433,7 @@
         setSimpleMappingStrategyValues();
         loadAmbientLightSensorFromConfigXml();
         setProxSensorUnspecified();
+        loadAutoBrightnessAvailableFromConfigXml();
     }
 
     private void copyUninitializedValuesFromSecondaryConfig(DisplayConfiguration defaultConfig) {
@@ -1559,9 +1626,11 @@
     }
 
     private void loadAutoBrightnessConfigValues(DisplayConfiguration config) {
-        loadAutoBrightnessBrighteningLightDebounce(config.getAutoBrightness());
-        loadAutoBrightnessDarkeningLightDebounce(config.getAutoBrightness());
-        loadAutoBrightnessDisplayBrightnessMapping(config.getAutoBrightness());
+        final AutoBrightness autoBrightness = config.getAutoBrightness();
+        loadAutoBrightnessBrighteningLightDebounce(autoBrightness);
+        loadAutoBrightnessDarkeningLightDebounce(autoBrightness);
+        loadAutoBrightnessDisplayBrightnessMapping(autoBrightness);
+        loadEnableAutoBrightness(autoBrightness);
     }
 
     /**
@@ -1623,6 +1692,11 @@
         }
     }
 
+    private void loadAutoBrightnessAvailableFromConfigXml() {
+        mAutoBrightnessAvailable = mContext.getResources().getBoolean(
+                R.bool.config_automatic_brightness_available);
+    }
+
     private void loadBrightnessMapFromConfigXml() {
         // Use the config.xml mapping
         final Resources res = mContext.getResources();
@@ -2263,6 +2337,20 @@
         return levels;
     }
 
+    private void loadEnableAutoBrightness(AutoBrightness autobrightness) {
+        // mDdcAutoBrightnessAvailable is initialised to true, so that we fallback to using the
+        // config.xml values if the autobrightness tag is not defined in the ddc file.
+        // Autobrightness can still be turned off globally via config_automatic_brightness_available
+        mDdcAutoBrightnessAvailable = true;
+        if (autobrightness != null) {
+            mDdcAutoBrightnessAvailable = autobrightness.getEnabled();
+        }
+
+        mAutoBrightnessAvailable = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_automatic_brightness_available)
+                && mDdcAutoBrightnessAvailable;
+    }
+
     static class SensorData {
         public String type;
         public String name;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index b153c1b..b8ff6ed 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1519,6 +1519,7 @@
             display.setUserDisabledHdrTypes(mUserDisabledHdrTypes);
         }
         if (isDefault) {
+            notifyDefaultDisplayDeviceUpdated(display);
             recordStableDisplayStatsIfNeededLocked(display);
             recordTopInsetLocked(display);
         }
@@ -1612,6 +1613,11 @@
             mHandler.post(work);
         }
         final int displayId = display.getDisplayIdLocked();
+
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            notifyDefaultDisplayDeviceUpdated(display);
+        }
+
         DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
         if (dpc != null) {
             dpc.onDisplayChanged();
@@ -1621,6 +1627,11 @@
         handleLogicalDisplayChangedLocked(display);
     }
 
+    private void notifyDefaultDisplayDeviceUpdated(LogicalDisplay display) {
+        mDisplayModeDirector.defaultDisplayDeviceUpdated(display.getPrimaryDisplayDeviceLocked()
+                .mDisplayDeviceConfig);
+    }
+
     private void handleLogicalDisplayDeviceStateTransitionLocked(@NonNull LogicalDisplay display) {
         final int displayId = display.getDisplayIdLocked();
         final DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
@@ -1759,9 +1770,13 @@
         if (displayDevice == null) {
             return;
         }
-        mPersistentDataStore.setUserPreferredResolution(
-                displayDevice, resolutionWidth, resolutionHeight);
-        mPersistentDataStore.setUserPreferredRefreshRate(displayDevice, refreshRate);
+        try {
+            mPersistentDataStore.setUserPreferredResolution(
+                    displayDevice, resolutionWidth, resolutionHeight);
+            mPersistentDataStore.setUserPreferredRefreshRate(displayDevice, refreshRate);
+        } finally {
+            mPersistentDataStore.saveIfNeeded();
+        }
     }
 
     private void setUserPreferredModeForDisplayLocked(int displayId, Display.Mode mode) {
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index ac72b17..912b1b2 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -79,6 +79,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
+import java.util.concurrent.Callable;
 
 /**
  * The DisplayModeDirector is responsible for determining what modes are allowed to be automatically
@@ -153,8 +154,10 @@
         mSupportedModesByDisplay = new SparseArray<>();
         mDefaultModeByDisplay = new SparseArray<>();
         mAppRequestObserver = new AppRequestObserver();
-        mSettingsObserver = new SettingsObserver(context, handler);
         mDisplayObserver = new DisplayObserver(context, handler);
+        mDeviceConfig = injector.getDeviceConfig();
+        mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
+        mSettingsObserver = new SettingsObserver(context, handler);
         mBrightnessObserver = new BrightnessObserver(context, handler, injector);
         mUdfpsObserver = new UdfpsObserver();
         final BallotBox ballotBox = (displayId, priority, vote) -> {
@@ -164,10 +167,8 @@
         };
         mSensorObserver = new SensorObserver(context, ballotBox, injector);
         mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, ballotBox);
-        mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
         mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler(),
                 mDeviceConfigDisplaySettings);
-        mDeviceConfig = injector.getDeviceConfig();
         mAlwaysRespectAppRequest = false;
     }
 
@@ -518,6 +519,15 @@
     }
 
     /**
+     * A utility to make this class aware of the new display configs whenever the default display is
+     * changed
+     */
+    public void defaultDisplayDeviceUpdated(DisplayDeviceConfig displayDeviceConfig) {
+        mSettingsObserver.setRefreshRates(displayDeviceConfig);
+        mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig);
+    }
+
+    /**
      * When enabled the app requested display mode is always selected and all
      * other votes will be ignored. This is used for testing purposes.
      */
@@ -1132,10 +1142,19 @@
         SettingsObserver(@NonNull Context context, @NonNull Handler handler) {
             super(handler);
             mContext = context;
-            mDefaultPeakRefreshRate = (float) context.getResources().getInteger(
-                    R.integer.config_defaultPeakRefreshRate);
+            setRefreshRates(/* displayDeviceConfig= */ null);
+        }
+
+        /**
+         * This is used to update the refresh rate configs from the DeviceConfig, which
+         * if missing from DisplayDeviceConfig, and finally fallback to config.xml.
+         */
+        public void setRefreshRates(DisplayDeviceConfig displayDeviceConfig) {
+            setDefaultPeakRefreshRate(displayDeviceConfig);
             mDefaultRefreshRate =
-                    (float) context.getResources().getInteger(R.integer.config_defaultRefreshRate);
+                    (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
+                            R.integer.config_defaultRefreshRate)
+                            : (float) displayDeviceConfig.getDefaultRefreshRate();
         }
 
         public void observe() {
@@ -1196,6 +1215,23 @@
             }
         }
 
+        private void setDefaultPeakRefreshRate(DisplayDeviceConfig displayDeviceConfig) {
+            Float defaultPeakRefreshRate = null;
+            try {
+                defaultPeakRefreshRate =
+                        mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate();
+            } catch (Exception exception) {
+                // Do nothing
+            }
+            if (defaultPeakRefreshRate == null) {
+                defaultPeakRefreshRate =
+                        (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
+                                R.integer.config_defaultPeakRefreshRate)
+                                : (float) displayDeviceConfig.getDefaultPeakRefreshRate();
+            }
+            mDefaultPeakRefreshRate = defaultPeakRefreshRate;
+        }
+
         private void updateLowPowerModeSettingLocked() {
             boolean inLowPowerMode = Settings.Global.getInt(mContext.getContentResolver(),
                     Settings.Global.LOW_POWER_MODE, 0 /*default*/) != 0;
@@ -1508,12 +1544,31 @@
             mContext = context;
             mHandler = handler;
             mInjector = injector;
+            updateBlockingZoneThresholds(/* displayDeviceConfig= */ null);
+            mRefreshRateInHighZone = context.getResources().getInteger(
+                    R.integer.config_fixedRefreshRateInHighZone);
+        }
 
-            mLowDisplayBrightnessThresholds = context.getResources().getIntArray(
-                    R.array.config_brightnessThresholdsOfPeakRefreshRate);
-            mLowAmbientBrightnessThresholds = context.getResources().getIntArray(
-                    R.array.config_ambientThresholdsOfPeakRefreshRate);
+        /**
+         * This is used to update the blocking zone thresholds from the DeviceConfig, which
+         * if missing from DisplayDeviceConfig, and finally fallback to config.xml.
+         */
+        public void updateBlockingZoneThresholds(DisplayDeviceConfig displayDeviceConfig) {
+            loadLowBrightnessThresholds(displayDeviceConfig);
+            loadHighBrightnessThresholds(displayDeviceConfig);
+        }
 
+        private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig) {
+            mLowDisplayBrightnessThresholds = loadBrightnessThresholds(
+                    () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(),
+                    () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(),
+                    R.array.config_brightnessThresholdsOfPeakRefreshRate,
+                    displayDeviceConfig);
+            mLowAmbientBrightnessThresholds = loadBrightnessThresholds(
+                    () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(),
+                    () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(),
+                    R.array.config_ambientThresholdsOfPeakRefreshRate,
+                    displayDeviceConfig);
             if (mLowDisplayBrightnessThresholds.length != mLowAmbientBrightnessThresholds.length) {
                 throw new RuntimeException("display low brightness threshold array and ambient "
                         + "brightness threshold array have different length: "
@@ -1522,11 +1577,19 @@
                         + ", ambientBrightnessThresholds="
                         + Arrays.toString(mLowAmbientBrightnessThresholds));
             }
+        }
 
-            mHighDisplayBrightnessThresholds = context.getResources().getIntArray(
-                    R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate);
-            mHighAmbientBrightnessThresholds = context.getResources().getIntArray(
-                    R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate);
+        private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig) {
+            mHighDisplayBrightnessThresholds = loadBrightnessThresholds(
+                    () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(),
+                    () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(),
+                    R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate,
+                    displayDeviceConfig);
+            mHighAmbientBrightnessThresholds = loadBrightnessThresholds(
+                    () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(),
+                    () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(),
+                    R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate,
+                    displayDeviceConfig);
             if (mHighDisplayBrightnessThresholds.length
                     != mHighAmbientBrightnessThresholds.length) {
                 throw new RuntimeException("display high brightness threshold array and ambient "
@@ -1536,8 +1599,32 @@
                         + ", ambientBrightnessThresholds="
                         + Arrays.toString(mHighAmbientBrightnessThresholds));
             }
-            mRefreshRateInHighZone = context.getResources().getInteger(
-                    R.integer.config_fixedRefreshRateInHighZone);
+        }
+
+        private int[] loadBrightnessThresholds(
+                Callable<int[]> loadFromDeviceConfigDisplaySettingsCallable,
+                Callable<int[]> loadFromDisplayDeviceConfigCallable,
+                int brightnessThresholdOfFixedRefreshRateKey,
+                DisplayDeviceConfig displayDeviceConfig) {
+            int[] brightnessThresholds = null;
+            try {
+                brightnessThresholds =
+                        loadFromDeviceConfigDisplaySettingsCallable.call();
+            } catch (Exception exception) {
+                // Do nothing
+            }
+            if (brightnessThresholds == null) {
+                try {
+                    brightnessThresholds =
+                            (displayDeviceConfig == null) ? mContext.getResources().getIntArray(
+                                    brightnessThresholdOfFixedRefreshRateKey)
+                                    : loadFromDisplayDeviceConfigCallable.call();
+                } catch (Exception e) {
+                    Slog.e(TAG, "Unexpectedly failed to load display brightness threshold");
+                    e.printStackTrace();
+                }
+            }
+            return brightnessThresholds;
         }
 
         /**
@@ -1590,7 +1677,6 @@
                 mLowAmbientBrightnessThresholds = lowAmbientBrightnessThresholds;
             }
 
-
             int[] highDisplayBrightnessThresholds =
                     mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds();
             int[] highAmbientBrightnessThresholds =
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 458cf1a..95dc23f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -558,13 +558,6 @@
         mScreenBrightnessForVrRangeMinimum = clampAbsoluteBrightness(
                 pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR));
 
-        // Check the setting, but also verify that it is the default display. Only the default
-        // display has an automatic brightness controller running.
-        // TODO: b/179021925 - Fix to work with multiple displays
-        mUseSoftwareAutoBrightnessConfig = resources.getBoolean(
-                com.android.internal.R.bool.config_automatic_brightness_available)
-                && mDisplayId == Display.DEFAULT_DISPLAY;
-
         mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
                 com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing);
 
@@ -938,6 +931,8 @@
     }
 
     private void setUpAutoBrightness(Resources resources, Handler handler) {
+        mUseSoftwareAutoBrightnessConfig = mDisplayDeviceConfig.isAutoBrightnessAvailable();
+
         if (!mUseSoftwareAutoBrightnessConfig) {
             return;
         }
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index b9a0738..a11f172 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -75,6 +75,11 @@
  *                  &lt;/brightness-curve>
  *              &lt;/brightness-configuration>
  *          &lt;/brightness-configurations>
+ *          &lt;display-mode>0&lt;
+ *              &lt;resolution-width>1080&lt;/resolution-width>
+ *              &lt;resolution-height>1920&lt;/resolution-height>
+ *              &lt;refresh-rate>60&lt;/refresh-rate>
+ *          &lt;/display-mode>
  *      &lt;/display>
  *  &lt;/display-states>
  *  &lt;stable-device-values>
@@ -121,6 +126,10 @@
     private static final String ATTR_PACKAGE_NAME = "package-name";
     private static final String ATTR_TIME_STAMP = "timestamp";
 
+    private static final String TAG_RESOLUTION_WIDTH = "resolution-width";
+    private static final String TAG_RESOLUTION_HEIGHT = "resolution-height";
+    private static final String TAG_REFRESH_RATE = "refresh-rate";
+
     // Remembered Wifi display devices.
     private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>();
 
@@ -696,6 +705,18 @@
                     case TAG_BRIGHTNESS_CONFIGURATIONS:
                         mDisplayBrightnessConfigurations.loadFromXml(parser);
                         break;
+                    case TAG_RESOLUTION_WIDTH:
+                        String width = parser.nextText();
+                        mWidth = Integer.parseInt(width);
+                        break;
+                    case TAG_RESOLUTION_HEIGHT:
+                        String height = parser.nextText();
+                        mHeight = Integer.parseInt(height);
+                        break;
+                    case TAG_REFRESH_RATE:
+                        String refreshRate = parser.nextText();
+                        mRefreshRate = Float.parseFloat(refreshRate);
+                        break;
                 }
             }
         }
@@ -712,6 +733,18 @@
             serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
             mDisplayBrightnessConfigurations.saveToXml(serializer);
             serializer.endTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
+
+            serializer.startTag(null, TAG_RESOLUTION_WIDTH);
+            serializer.text(Integer.toString(mWidth));
+            serializer.endTag(null, TAG_RESOLUTION_WIDTH);
+
+            serializer.startTag(null, TAG_RESOLUTION_HEIGHT);
+            serializer.text(Integer.toString(mHeight));
+            serializer.endTag(null, TAG_RESOLUTION_HEIGHT);
+
+            serializer.startTag(null, TAG_REFRESH_RATE);
+            serializer.text(Float.toString(mRefreshRate));
+            serializer.endTag(null, TAG_REFRESH_RATE);
         }
 
         public void dump(final PrintWriter pw, final String prefix) {
@@ -719,6 +752,8 @@
             pw.println(prefix + "BrightnessValue=" + mBrightness);
             pw.println(prefix + "DisplayBrightnessConfigurations: ");
             mDisplayBrightnessConfigurations.dump(pw, prefix);
+            pw.println(prefix + "Resolution=" + mWidth + " " + mHeight);
+            pw.println(prefix + "RefreshRate=" + mRefreshRate);
         }
     }
 
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index b8af1bf..b11a06e 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -34,13 +34,13 @@
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamService;
 import android.util.Slog;
-import android.view.IWindowManager;
-import android.view.WindowManagerGlobal;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.NoSuchElementException;
 
 /**
@@ -60,9 +60,6 @@
     private final Context mContext;
     private final Handler mHandler;
     private final Listener mListener;
-    private final IWindowManager mIWindowManager;
-    private long mDreamStartTime;
-    private String mSavedStopReason;
 
     private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED)
             .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -73,27 +70,20 @@
 
     private DreamRecord mCurrentDream;
 
-    private final Runnable mStopUnconnectedDreamRunnable = new Runnable() {
-        @Override
-        public void run() {
-            if (mCurrentDream != null && mCurrentDream.mBound && !mCurrentDream.mConnected) {
-                Slog.w(TAG, "Bound dream did not connect in the time allotted");
-                stopDream(true /*immediate*/, "slow to connect");
-            }
-        }
-    };
+    // Whether a dreaming started intent has been broadcast.
+    private boolean mSentStartBroadcast = false;
 
-    private final Runnable mStopStubbornDreamRunnable = () -> {
-        Slog.w(TAG, "Stubborn dream did not finish itself in the time allotted");
-        stopDream(true /*immediate*/, "slow to finish");
-        mSavedStopReason = null;
-    };
+    // When a new dream is started and there is an existing dream, the existing dream is allowed to
+    // live a little longer until the new dream is started, for a smoother transition. This dream is
+    // stopped as soon as the new dream is started, and this list is cleared. Usually there should
+    // only be one previous dream while waiting for a new dream to start, but we store a list to
+    // proof the edge case of multiple previous dreams.
+    private final ArrayList<DreamRecord> mPreviousDreams = new ArrayList<>();
 
     public DreamController(Context context, Handler handler, Listener listener) {
         mContext = context;
         mHandler = handler;
         mListener = listener;
-        mIWindowManager = WindowManagerGlobal.getWindowManagerService();
         mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         mCloseNotificationShadeIntent.putExtra("reason", "dream");
     }
@@ -109,18 +99,17 @@
             pw.println("    mUserId=" + mCurrentDream.mUserId);
             pw.println("    mBound=" + mCurrentDream.mBound);
             pw.println("    mService=" + mCurrentDream.mService);
-            pw.println("    mSentStartBroadcast=" + mCurrentDream.mSentStartBroadcast);
             pw.println("    mWakingGently=" + mCurrentDream.mWakingGently);
         } else {
             pw.println("  mCurrentDream: null");
         }
+
+        pw.println("  mSentStartBroadcast=" + mSentStartBroadcast);
     }
 
     public void startDream(Binder token, ComponentName name,
             boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock,
             ComponentName overlayComponentName, String reason) {
-        stopDream(true /*immediate*/, "starting new dream");
-
         Trace.traceBegin(Trace.TRACE_TAG_POWER, "startDream");
         try {
             // Close the notification shade. No need to send to all, but better to be explicit.
@@ -130,9 +119,12 @@
                     + ", isPreviewMode=" + isPreviewMode + ", canDoze=" + canDoze
                     + ", userId=" + userId + ", reason='" + reason + "'");
 
+            if (mCurrentDream != null) {
+                mPreviousDreams.add(mCurrentDream);
+            }
             mCurrentDream = new DreamRecord(token, name, isPreviewMode, canDoze, userId, wakeLock);
 
-            mDreamStartTime = SystemClock.elapsedRealtime();
+            mCurrentDream.mDreamStartTime = SystemClock.elapsedRealtime();
             MetricsLogger.visible(mContext,
                     mCurrentDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING);
 
@@ -155,31 +147,49 @@
             }
 
             mCurrentDream.mBound = true;
-            mHandler.postDelayed(mStopUnconnectedDreamRunnable, DREAM_CONNECTION_TIMEOUT);
+            mHandler.postDelayed(mCurrentDream.mStopUnconnectedDreamRunnable,
+                    DREAM_CONNECTION_TIMEOUT);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_POWER);
         }
     }
 
+    /**
+     * Stops dreaming.
+     *
+     * The current dream, if any, and any unstopped previous dreams are stopped. The device stops
+     * dreaming.
+     */
     public void stopDream(boolean immediate, String reason) {
-        if (mCurrentDream == null) {
+        stopPreviousDreams();
+        stopDreamInstance(immediate, reason, mCurrentDream);
+    }
+
+    /**
+     * Stops the given dream instance.
+     *
+     * The device may still be dreaming afterwards if there are other dreams running.
+     */
+    private void stopDreamInstance(boolean immediate, String reason, DreamRecord dream) {
+        if (dream == null) {
             return;
         }
 
         Trace.traceBegin(Trace.TRACE_TAG_POWER, "stopDream");
         try {
             if (!immediate) {
-                if (mCurrentDream.mWakingGently) {
+                if (dream.mWakingGently) {
                     return; // already waking gently
                 }
 
-                if (mCurrentDream.mService != null) {
+                if (dream.mService != null) {
                     // Give the dream a moment to wake up and finish itself gently.
-                    mCurrentDream.mWakingGently = true;
+                    dream.mWakingGently = true;
                     try {
-                        mSavedStopReason = reason;
-                        mCurrentDream.mService.wakeUp();
-                        mHandler.postDelayed(mStopStubbornDreamRunnable, DREAM_FINISH_TIMEOUT);
+                        dream.mStopReason = reason;
+                        dream.mService.wakeUp();
+                        mHandler.postDelayed(dream.mStopStubbornDreamRunnable,
+                                DREAM_FINISH_TIMEOUT);
                         return;
                     } catch (RemoteException ex) {
                         // oh well, we tried, finish immediately instead
@@ -187,54 +197,73 @@
                 }
             }
 
-            final DreamRecord oldDream = mCurrentDream;
-            mCurrentDream = null;
-            Slog.i(TAG, "Stopping dream: name=" + oldDream.mName
-                    + ", isPreviewMode=" + oldDream.mIsPreviewMode
-                    + ", canDoze=" + oldDream.mCanDoze
-                    + ", userId=" + oldDream.mUserId
+            Slog.i(TAG, "Stopping dream: name=" + dream.mName
+                    + ", isPreviewMode=" + dream.mIsPreviewMode
+                    + ", canDoze=" + dream.mCanDoze
+                    + ", userId=" + dream.mUserId
                     + ", reason='" + reason + "'"
-                    + (mSavedStopReason == null ? "" : "(from '" + mSavedStopReason + "')"));
+                    + (dream.mStopReason == null ? "" : "(from '"
+                    + dream.mStopReason + "')"));
             MetricsLogger.hidden(mContext,
-                    oldDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING);
+                    dream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING);
             MetricsLogger.histogram(mContext,
-                    oldDream.mCanDoze ? "dozing_minutes" : "dreaming_minutes" ,
-                    (int) ((SystemClock.elapsedRealtime() - mDreamStartTime) / (1000L * 60L)));
+                    dream.mCanDoze ? "dozing_minutes" : "dreaming_minutes",
+                    (int) ((SystemClock.elapsedRealtime() - dream.mDreamStartTime) / (1000L
+                            * 60L)));
 
-            mHandler.removeCallbacks(mStopUnconnectedDreamRunnable);
-            mHandler.removeCallbacks(mStopStubbornDreamRunnable);
-            mSavedStopReason = null;
+            mHandler.removeCallbacks(dream.mStopUnconnectedDreamRunnable);
+            mHandler.removeCallbacks(dream.mStopStubbornDreamRunnable);
 
-            if (oldDream.mSentStartBroadcast) {
-                mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL);
-            }
-
-            if (oldDream.mService != null) {
+            if (dream.mService != null) {
                 try {
-                    oldDream.mService.detach();
+                    dream.mService.detach();
                 } catch (RemoteException ex) {
                     // we don't care; this thing is on the way out
                 }
 
                 try {
-                    oldDream.mService.asBinder().unlinkToDeath(oldDream, 0);
+                    dream.mService.asBinder().unlinkToDeath(dream, 0);
                 } catch (NoSuchElementException ex) {
                     // don't care
                 }
-                oldDream.mService = null;
+                dream.mService = null;
             }
 
-            if (oldDream.mBound) {
-                mContext.unbindService(oldDream);
+            if (dream.mBound) {
+                mContext.unbindService(dream);
             }
-            oldDream.releaseWakeLockIfNeeded();
+            dream.releaseWakeLockIfNeeded();
 
-            mHandler.post(() -> mListener.onDreamStopped(oldDream.mToken));
+            // Current dream stopped, device no longer dreaming.
+            if (dream == mCurrentDream) {
+                mCurrentDream = null;
+
+                if (mSentStartBroadcast) {
+                    mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL);
+                }
+
+                mListener.onDreamStopped(dream.mToken);
+            }
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_POWER);
         }
     }
 
+    /**
+     * Stops all previous dreams, if any.
+     */
+    private void stopPreviousDreams() {
+        if (mPreviousDreams.isEmpty()) {
+            return;
+        }
+
+        // Using an iterator because mPreviousDreams is modified while the iteration is in process.
+        for (final Iterator<DreamRecord> it = mPreviousDreams.iterator(); it.hasNext(); ) {
+            stopDreamInstance(true /*immediate*/, "stop previous dream", it.next());
+            it.remove();
+        }
+    }
+
     private void attach(IDreamService service) {
         try {
             service.asBinder().linkToDeath(mCurrentDream, 0);
@@ -248,9 +277,9 @@
 
         mCurrentDream.mService = service;
 
-        if (!mCurrentDream.mIsPreviewMode) {
+        if (!mCurrentDream.mIsPreviewMode && !mSentStartBroadcast) {
             mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL);
-            mCurrentDream.mSentStartBroadcast = true;
+            mSentStartBroadcast = true;
         }
     }
 
@@ -272,10 +301,35 @@
         public boolean mBound;
         public boolean mConnected;
         public IDreamService mService;
-        public boolean mSentStartBroadcast;
-
+        private String mStopReason;
+        private long mDreamStartTime;
         public boolean mWakingGently;
 
+        private final Runnable mStopPreviousDreamsIfNeeded = this::stopPreviousDreamsIfNeeded;
+        private final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded;
+
+        private final Runnable mStopUnconnectedDreamRunnable = () -> {
+            if (mBound && !mConnected) {
+                Slog.w(TAG, "Bound dream did not connect in the time allotted");
+                stopDream(true /*immediate*/, "slow to connect" /*reason*/);
+            }
+        };
+
+        private final Runnable mStopStubbornDreamRunnable = () -> {
+            Slog.w(TAG, "Stubborn dream did not finish itself in the time allotted");
+            stopDream(true /*immediate*/, "slow to finish" /*reason*/);
+            mStopReason = null;
+        };
+
+        private final IRemoteCallback mDreamingStartedCallback = new IRemoteCallback.Stub() {
+            // May be called on any thread.
+            @Override
+            public void sendResult(Bundle data) {
+                mHandler.post(mStopPreviousDreamsIfNeeded);
+                mHandler.post(mReleaseWakeLockIfNeeded);
+            }
+        };
+
         DreamRecord(Binder token, ComponentName name, boolean isPreviewMode,
                 boolean canDoze, int userId, PowerManager.WakeLock wakeLock) {
             mToken = token;
@@ -286,7 +340,9 @@
             mWakeLock = wakeLock;
             // Hold the lock while we're waiting for the service to connect and start dreaming.
             // Released after the service has started dreaming, we stop dreaming, or it timed out.
-            mWakeLock.acquire();
+            if (mWakeLock != null) {
+                mWakeLock.acquire();
+            }
             mHandler.postDelayed(mReleaseWakeLockIfNeeded, 10000);
         }
 
@@ -326,6 +382,12 @@
             });
         }
 
+        void stopPreviousDreamsIfNeeded() {
+            if (mCurrentDream == DreamRecord.this) {
+                stopPreviousDreams();
+            }
+        }
+
         void releaseWakeLockIfNeeded() {
             if (mWakeLock != null) {
                 mWakeLock.release();
@@ -333,15 +395,5 @@
                 mHandler.removeCallbacks(mReleaseWakeLockIfNeeded);
             }
         }
-
-        final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded;
-
-        final IRemoteCallback mDreamingStartedCallback = new IRemoteCallback.Stub() {
-            // May be called on any thread.
-            @Override
-            public void sendResult(Bundle data) throws RemoteException {
-                mHandler.post(mReleaseWakeLockIfNeeded);
-            }
-        };
     }
 }
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 7964fd5..5589673 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -246,8 +246,8 @@
         // Because napping could cause the screen to turn off immediately if the dream
         // cannot be started, we keep one eye open and gently poke user activity.
         long time = SystemClock.uptimeMillis();
-        mPowerManager.userActivity(time, true /*noChangeLights*/);
-        mPowerManager.nap(time);
+        mPowerManager.userActivity(time, /* noChangeLights= */ true);
+        mPowerManagerInternal.nap(time, /* allowWake= */ true);
     }
 
     private void requestAwakenInternal(String reason) {
@@ -493,8 +493,6 @@
             return;
         }
 
-        stopDreamLocked(true /*immediate*/, "starting new dream");
-
         Slog.i(TAG, "Entering dreamland.");
 
         mCurrentDream = new DreamRecord(name, userId, isPreviewMode, canDoze);
@@ -637,7 +635,7 @@
                 @Nullable FileDescriptor err,
                 @NonNull String[] args, @Nullable ShellCallback callback,
                 @NonNull ResultReceiver resultReceiver) throws RemoteException {
-            new DreamShellCommand(DreamManagerService.this, mPowerManager)
+            new DreamShellCommand(DreamManagerService.this)
                     .exec(this, in, out, err, args, callback, resultReceiver);
         }
 
diff --git a/services/core/java/com/android/server/dreams/DreamShellCommand.java b/services/core/java/com/android/server/dreams/DreamShellCommand.java
index eae7e80..ab84ae4 100644
--- a/services/core/java/com/android/server/dreams/DreamShellCommand.java
+++ b/services/core/java/com/android/server/dreams/DreamShellCommand.java
@@ -18,10 +18,8 @@
 
 import android.annotation.NonNull;
 import android.os.Binder;
-import android.os.PowerManager;
 import android.os.Process;
 import android.os.ShellCommand;
-import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.Slog;
 
@@ -34,11 +32,9 @@
     private static final boolean DEBUG = true;
     private static final String TAG = "DreamShellCommand";
     private final @NonNull DreamManagerService mService;
-    private final @NonNull PowerManager mPowerManager;
 
-    DreamShellCommand(@NonNull DreamManagerService service, @NonNull PowerManager powerManager) {
+    DreamShellCommand(@NonNull DreamManagerService service) {
         mService = service;
-        mPowerManager = powerManager;
     }
 
     @Override
@@ -67,8 +63,6 @@
     }
 
     private int startDreaming() {
-        mPowerManager.wakeUp(SystemClock.uptimeMillis(),
-                PowerManager.WAKE_REASON_PLUGGED_IN, "shell:cmd:android.service.dreams:DREAM");
         mService.requestStartDreamFromShell();
         return 0;
     }
diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
index 6759d79..9a19031 100644
--- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
+++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
@@ -32,7 +32,6 @@
 import android.os.PowerWhitelistManager;
 import android.os.UserHandle;
 import android.text.TextUtils;
-import android.util.EventLog;
 import android.util.Log;
 import android.view.KeyEvent;
 
@@ -118,12 +117,6 @@
         int componentType = getComponentType(pendingIntent);
         ComponentName componentName = getComponentName(pendingIntent, componentType);
         if (componentName != null) {
-            if (!TextUtils.equals(componentName.getPackageName(), sessionPackageName)) {
-                EventLog.writeEvent(0x534e4554, "238177121", -1, ""); // SafetyNet logging
-                throw new IllegalArgumentException("ComponentName does not belong to "
-                        + "sessionPackageName. sessionPackageName = " + sessionPackageName
-                        + ", ComponentName pkg = " + componentName.getPackageName());
-            }
             return new MediaButtonReceiverHolder(userId, pendingIntent, componentName,
                     componentType);
         }
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index b8131a8..604e8f3 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -52,8 +52,6 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.EventLog;
 import android.util.Log;
 import android.view.KeyEvent;
 
@@ -940,14 +938,6 @@
         @Override
         public void setMediaButtonReceiver(PendingIntent pi, String sessionPackageName)
                 throws RemoteException {
-            //mPackageName has been verified in MediaSessionService.enforcePackageName().
-            if (!TextUtils.equals(sessionPackageName, mPackageName)) {
-                EventLog.writeEvent(0x534e4554, "238177121", -1, ""); // SafetyNet logging
-                throw new IllegalArgumentException("sessionPackageName name does not match "
-                        + "package name provided to MediaSessionRecord. sessionPackageName = "
-                        + sessionPackageName + ", pkg = "
-                        + mPackageName);
-            }
             final long token = Binder.clearCallingIdentity();
             try {
                 if ((mPolicies & MediaSessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER)
@@ -966,15 +956,6 @@
         public void setMediaButtonBroadcastReceiver(ComponentName receiver) throws RemoteException {
             final long token = Binder.clearCallingIdentity();
             try {
-                //mPackageName has been verified in MediaSessionService.enforcePackageName().
-                if (receiver != null && !TextUtils.equals(
-                        mPackageName, receiver.getPackageName())) {
-                    EventLog.writeEvent(0x534e4554, "238177121", -1, ""); // SafetyNet logging
-                    throw new IllegalArgumentException("receiver does not belong to "
-                            + "package name provided to MediaSessionRecord. Pkg = " + mPackageName
-                            + ", Receiver Pkg = " + receiver.getPackageName());
-                }
-
                 if ((mPolicies & MediaSessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER)
                         != 0) {
                     return;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index fd1fdce..a55b118 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -61,6 +61,7 @@
 import static android.content.pm.PackageManager.FEATURE_TELECOM;
 import static android.content.pm.PackageManager.FEATURE_TELEVISION;
 import static android.content.pm.PackageManager.MATCH_ALL;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -10633,10 +10634,18 @@
         private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>();
         ArrayMap<Pair<ComponentName, Integer>, NotificationListenerFilter>
                 mRequestedNotificationListeners = new ArrayMap<>();
+        private final boolean mIsHeadlessSystemUserMode;
 
         public NotificationListeners(Context context, Object lock, UserProfiles userProfiles,
                 IPackageManager pm) {
+            this(context, lock, userProfiles, pm, UserManager.isHeadlessSystemUserMode());
+        }
+
+        @VisibleForTesting
+        public NotificationListeners(Context context, Object lock, UserProfiles userProfiles,
+                IPackageManager pm, boolean isHeadlessSystemUserMode) {
             super(context, lock, userProfiles, pm);
+            this.mIsHeadlessSystemUserMode = isHeadlessSystemUserMode;
         }
 
         @Override
@@ -10661,10 +10670,16 @@
                     if (TextUtils.isEmpty(listeners[i])) {
                         continue;
                     }
+                    int packageQueryFlags = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
+                    // In the headless system user mode, packages might not be installed for the
+                    // system user. Match packages for any user since apps can be installed only for
+                    // non-system users and would be considering uninstalled for the system user.
+                    if (mIsHeadlessSystemUserMode) {
+                        packageQueryFlags += MATCH_ANY_USER;
+                    }
                     ArraySet<ComponentName> approvedListeners =
-                            this.queryPackageForServices(listeners[i],
-                                    MATCH_DIRECT_BOOT_AWARE
-                                            | MATCH_DIRECT_BOOT_UNAWARE, USER_SYSTEM);
+                            this.queryPackageForServices(listeners[i], packageQueryFlags,
+                                    USER_SYSTEM);
                     for (int k = 0; k < approvedListeners.size(); k++) {
                         ComponentName cn = approvedListeners.valueAt(k);
                         addDefaultComponentOrPackage(cn.flattenToString());
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 014d580..18c29fa 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -644,8 +644,8 @@
             Permission bp = mRegistry.getPermission(info.name);
             added = bp == null;
             int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel);
+            enforcePermissionCapLocked(info, tree);
             if (added) {
-                enforcePermissionCapLocked(info, tree);
                 bp = new Permission(info.name, tree.getPackageName(), Permission.TYPE_DYNAMIC);
             } else if (!bp.isDynamic()) {
                 throw new SecurityException("Not allowed to modify non-dynamic permission "
diff --git a/services/core/java/com/android/server/power/AmbientDisplaySuppressionController.java b/services/core/java/com/android/server/power/AmbientDisplaySuppressionController.java
index aad7b14..7440fc7 100644
--- a/services/core/java/com/android/server/power/AmbientDisplaySuppressionController.java
+++ b/services/core/java/com/android/server/power/AmbientDisplaySuppressionController.java
@@ -40,13 +40,24 @@
 public class AmbientDisplaySuppressionController {
     private static final String TAG = "AmbientDisplaySuppressionController";
 
-    private final Context mContext;
     private final Set<Pair<String, Integer>> mSuppressionTokens;
+    private final AmbientDisplaySuppressionChangedCallback mCallback;
     private IStatusBarService mStatusBarService;
 
-    AmbientDisplaySuppressionController(Context context) {
-        mContext = requireNonNull(context);
+    /** Interface to get a list of available logical devices. */
+    interface AmbientDisplaySuppressionChangedCallback {
+        /**
+         * Called when the suppression state changes.
+         *
+         * @param isSuppressed Whether ambient is suppressed.
+         */
+        void onSuppressionChanged(boolean isSuppressed);
+    }
+
+    AmbientDisplaySuppressionController(
+            @NonNull AmbientDisplaySuppressionChangedCallback callback) {
         mSuppressionTokens = Collections.synchronizedSet(new ArraySet<>());
+        mCallback = requireNonNull(callback);
     }
 
     /**
@@ -58,6 +69,7 @@
      */
     public void suppress(@NonNull String token, int callingUid, boolean suppress) {
         Pair<String, Integer> suppressionToken = Pair.create(requireNonNull(token), callingUid);
+        final boolean wasSuppressed = isSuppressed();
 
         if (suppress) {
             mSuppressionTokens.add(suppressionToken);
@@ -65,9 +77,14 @@
             mSuppressionTokens.remove(suppressionToken);
         }
 
+        final boolean isSuppressed = isSuppressed();
+        if (isSuppressed != wasSuppressed) {
+            mCallback.onSuppressionChanged(isSuppressed);
+        }
+
         try {
             synchronized (mSuppressionTokens) {
-                getStatusBar().suppressAmbientDisplay(isSuppressed());
+                getStatusBar().suppressAmbientDisplay(isSuppressed);
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to suppress ambient display", e);
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 9fe53fb..431cf38 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -229,8 +229,8 @@
         }
     }
 
-    boolean dreamLocked(long eventTime, int uid) {
-        if (eventTime < mLastWakeTime || mWakefulness != WAKEFULNESS_AWAKE) {
+    boolean dreamLocked(long eventTime, int uid, boolean allowWake) {
+        if (eventTime < mLastWakeTime || (!allowWake && mWakefulness != WAKEFULNESS_AWAKE)) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index f200564..725fb3f 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -127,6 +127,7 @@
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
 import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.power.AmbientDisplaySuppressionController.AmbientDisplaySuppressionChangedCallback;
 import com.android.server.power.batterysaver.BatterySaverController;
 import com.android.server.power.batterysaver.BatterySaverPolicy;
 import com.android.server.power.batterysaver.BatterySaverStateMachine;
@@ -467,6 +468,9 @@
     // True if the device should wake up when plugged or unplugged.
     private boolean mWakeUpWhenPluggedOrUnpluggedConfig;
 
+    // True if the device should keep dreaming when undocked.
+    private boolean mKeepDreamingWhenUndockingConfig;
+
     // True if the device should wake up when plugged or unplugged in theater mode.
     private boolean mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig;
 
@@ -504,6 +508,9 @@
     // effectively and terminate the dream.  Use -1 to disable this safety feature.
     private int mDreamsBatteryLevelDrainCutoffConfig;
 
+    // Whether dreams should be disabled when ambient mode is suppressed.
+    private boolean mDreamsDisabledByAmbientModeSuppressionConfig;
+
     // True if dreams are enabled by the user.
     private boolean mDreamsEnabledSetting;
 
@@ -961,8 +968,8 @@
         }
 
         AmbientDisplaySuppressionController createAmbientDisplaySuppressionController(
-                Context context) {
-            return new AmbientDisplaySuppressionController(context);
+                @NonNull AmbientDisplaySuppressionChangedCallback callback) {
+            return new AmbientDisplaySuppressionController(callback);
         }
 
         InattentiveSleepWarningController createInattentiveSleepWarningController() {
@@ -1041,7 +1048,8 @@
         mConstants = new Constants(mHandler);
         mAmbientDisplayConfiguration = mInjector.createAmbientDisplayConfiguration(context);
         mAmbientDisplaySuppressionController =
-                mInjector.createAmbientDisplaySuppressionController(context);
+                mInjector.createAmbientDisplaySuppressionController(
+                        mAmbientSuppressionChangedCallback);
         mAttentionDetector = new AttentionDetector(this::onUserAttention, mLock);
         mFaceDownDetector = new FaceDownDetector(this::onFlip);
         mScreenUndimDetector = new ScreenUndimDetector();
@@ -1374,6 +1382,8 @@
                 com.android.internal.R.bool.config_powerDecoupleInteractiveModeFromDisplay);
         mWakeUpWhenPluggedOrUnpluggedConfig = resources.getBoolean(
                 com.android.internal.R.bool.config_unplugTurnsOnScreen);
+        mKeepDreamingWhenUndockingConfig = resources.getBoolean(
+                com.android.internal.R.bool.config_keepDreamingWhenUndocking);
         mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig = resources.getBoolean(
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromUnplug);
         mSuspendWhenScreenOffDueToProximityConfig = resources.getBoolean(
@@ -1398,6 +1408,8 @@
                 com.android.internal.R.integer.config_dreamsBatteryLevelMinimumWhenNotPowered);
         mDreamsBatteryLevelDrainCutoffConfig = resources.getInteger(
                 com.android.internal.R.integer.config_dreamsBatteryLevelDrainCutoff);
+        mDreamsDisabledByAmbientModeSuppressionConfig = resources.getBoolean(
+                com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
         mDozeAfterScreenOff = resources.getBoolean(
                 com.android.internal.R.bool.config_dozeAfterScreenOffByDefault);
         mMinimumScreenOffTimeoutConfig = resources.getInteger(
@@ -1919,6 +1931,13 @@
         }
     }
 
+    private void napInternal(long eventTime, int uid, boolean allowWake) {
+        synchronized (mLock) {
+            dreamPowerGroupLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP),
+                    eventTime, uid, allowWake);
+        }
+    }
+
     private void onUserAttention() {
         synchronized (mLock) {
             if (userActivityNoUpdateLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP),
@@ -2034,7 +2053,8 @@
     }
 
     @GuardedBy("mLock")
-    private boolean dreamPowerGroupLocked(PowerGroup powerGroup, long eventTime, int uid) {
+    private boolean dreamPowerGroupLocked(PowerGroup powerGroup, long eventTime, int uid,
+            boolean allowWake) {
         if (DEBUG_SPEW) {
             Slog.d(TAG, "dreamPowerGroup: groupId=" + powerGroup.getGroupId() + ", eventTime="
                     + eventTime + ", uid=" + uid);
@@ -2042,7 +2062,7 @@
         if (!mBootCompleted || !mSystemReady) {
             return false;
         }
-        return powerGroup.dreamLocked(eventTime, uid);
+        return powerGroup.dreamLocked(eventTime, uid, allowWake);
     }
 
     @GuardedBy("mLock")
@@ -2487,6 +2507,14 @@
             return false;
         }
 
+        // Don't wake when undocking while dreaming if configured not to.
+        if (mKeepDreamingWhenUndockingConfig
+                && getGlobalWakefulnessLocked() == WAKEFULNESS_DREAMING
+                && wasPowered && !mIsPowered
+                && oldPlugType == BatteryManager.BATTERY_PLUGGED_DOCK) {
+            return false;
+        }
+
         // Don't wake when undocked from wireless charger.
         // See WirelessChargerDetector for justification.
         if (wasPowered && !mIsPowered
@@ -3086,7 +3114,8 @@
                 changed = sleepPowerGroupLocked(powerGroup, time,
                         PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE, Process.SYSTEM_UID);
             } else if (shouldNapAtBedTimeLocked()) {
-                changed = dreamPowerGroupLocked(powerGroup, time, Process.SYSTEM_UID);
+                changed = dreamPowerGroupLocked(powerGroup, time,
+                        Process.SYSTEM_UID, /* allowWake= */ false);
             } else {
                 changed = dozePowerGroupLocked(powerGroup, time,
                         PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, Process.SYSTEM_UID);
@@ -3307,7 +3336,7 @@
                 }
 
                 // Doze has ended or will be stopped.  Update the power state.
-                sleepPowerGroupLocked(powerGroup, now,  PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,
+                sleepPowerGroupLocked(powerGroup, now, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,
                         Process.SYSTEM_UID);
             }
         }
@@ -3318,12 +3347,32 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private void onDreamSuppressionChangedLocked(final boolean isSuppressed) {
+        if (!mDreamsDisabledByAmbientModeSuppressionConfig) {
+            return;
+        }
+        if (!isSuppressed && mIsPowered && mDreamsSupportedConfig && mDreamsEnabledSetting
+                && shouldNapAtBedTimeLocked() && isItBedTimeYetLocked(
+                mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP))) {
+            napInternal(SystemClock.uptimeMillis(), Process.SYSTEM_UID, /* allowWake= */ true);
+        } else if (isSuppressed) {
+            mDirty |= DIRTY_SETTINGS;
+            updatePowerStateLocked();
+        }
+    }
+
+
     /**
      * Returns true if the {@code groupId} is allowed to dream in its current state.
      */
     @GuardedBy("mLock")
     private boolean canDreamLocked(final PowerGroup powerGroup) {
+        final boolean dreamsSuppressed = mDreamsDisabledByAmbientModeSuppressionConfig
+                && mAmbientDisplaySuppressionController.isSuppressed();
+
         if (!mBootCompleted
+                || dreamsSuppressed
                 || getGlobalWakefulnessLocked() != WAKEFULNESS_DREAMING
                 || !mDreamsSupportedConfig
                 || !mDreamsEnabledSetting
@@ -4408,6 +4457,8 @@
                     + mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig);
             pw.println("  mTheaterModeEnabled="
                     + mTheaterModeEnabled);
+            pw.println("  mKeepDreamingWhenUndockingConfig="
+                    + mKeepDreamingWhenUndockingConfig);
             pw.println("  mSuspendWhenScreenOffDueToProximityConfig="
                     + mSuspendWhenScreenOffDueToProximityConfig);
             pw.println("  mDreamsSupportedConfig=" + mDreamsSupportedConfig);
@@ -5013,6 +5064,16 @@
         }
     };
 
+    private final AmbientDisplaySuppressionChangedCallback mAmbientSuppressionChangedCallback =
+            new AmbientDisplaySuppressionChangedCallback() {
+                @Override
+                public void onSuppressionChanged(boolean isSuppressed) {
+                    synchronized (mLock) {
+                        onDreamSuppressionChangedLocked(isSuppressed);
+                    }
+                }
+            };
+
     /**
      * Callback for asynchronous operations performed by the power manager.
      */
@@ -5701,10 +5762,7 @@
             final int uid = Binder.getCallingUid();
             final long ident = Binder.clearCallingIdentity();
             try {
-                synchronized (mLock) {
-                    dreamPowerGroupLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP),
-                            eventTime, uid);
-                }
+                napInternal(eventTime, uid, /* allowWake= */ false);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -6632,6 +6690,16 @@
         public boolean interceptPowerKeyDown(KeyEvent event) {
             return interceptPowerKeyDownInternal(event);
         }
+
+        @Override
+        public void nap(long eventTime, boolean allowWake) {
+            napInternal(eventTime, Process.SYSTEM_UID, allowWake);
+        }
+
+        @Override
+        public boolean isAmbientDisplaySuppressed() {
+            return mAmbientDisplaySuppressionController.isSuppressed();
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 653b51a9..3df8f58 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -2253,6 +2253,25 @@
 
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+        boolean proto = false;
+        for (int i = 0; i < args.length; i++) {
+            if ("--proto".equals(args[i])) {
+                proto = true;
+            }
+        }
+        if (proto) {
+            if (mBar == null)  return;
+            try (TransferPipe tp = new TransferPipe()) {
+                // Sending the command to the remote, which needs to execute async to avoid blocking
+                // See Binder#dumpAsync() for inspiration
+                mBar.dumpProto(args, tp.getWriteFd());
+                // Times out after 5s
+                tp.go(fd);
+            } catch (Throwable t) {
+                Slog.e(TAG, "Error sending command to IStatusBar", t);
+            }
+            return;
+        }
 
         synchronized (mLock) {
             for (int i = 0; i < mDisplayUiState.size(); i++) {
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 0799b95..9c455db 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -65,6 +65,9 @@
     public final DeviceVibrationEffectAdapter deviceEffectAdapter;
     public final VibrationThread.VibratorManagerHooks vibratorManagerHooks;
 
+    // Not guarded by lock because they're not modified by this conductor, it's used here only to
+    // check immutable attributes. The status and other mutable states are changed by the service or
+    // by the vibrator steps.
     private final Vibration mVibration;
     private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
 
@@ -405,6 +408,16 @@
         }
     }
 
+    /** Returns true if a cancellation signal was sent via {@link #notifyCancelled}. */
+    public boolean wasNotifiedToCancel() {
+        if (Build.IS_DEBUGGABLE) {
+            expectIsVibrationThread(false);
+        }
+        synchronized (mLock) {
+            return mSignalCancel != null;
+        }
+    }
+
     @GuardedBy("mLock")
     private boolean hasPendingNotifySignalLocked() {
         if (Build.IS_DEBUGGABLE) {
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 9727558..1469359 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -852,8 +852,8 @@
         }
 
         Vibration currentVibration = mCurrentVibration.getVibration();
-        if (currentVibration.hasEnded()) {
-            // Current vibration is finishing up, it should not block incoming vibrations.
+        if (currentVibration.hasEnded() || mCurrentVibration.wasNotifiedToCancel()) {
+            // Current vibration has ended or is cancelling, should not block incoming vibrations.
             return null;
         }
 
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index f25929c..189b86f 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1166,7 +1166,7 @@
                 try {
                     connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
                             wpdData.mWidth, wpdData.mHeight,
-                            wpdData.mPadding, mDisplayId);
+                            wpdData.mPadding, mDisplayId, FLAG_SYSTEM | FLAG_LOCK);
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Failed attaching wallpaper on display", e);
                     if (wallpaper != null && !wallpaper.wallpaperUpdating
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 30454d4..9f502ab 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -389,9 +389,9 @@
         SafeActivityOptions bottomOptions = null;
         if (options != null) {
             // To ensure the first N-1 activities (N == total # of activities) are also launched
-            // into the correct display, use a copy of the passed-in options (keeping only
-            // display-related info) for these activities.
-            bottomOptions = options.selectiveCloneDisplayOptions();
+            // into the correct display and root task, use a copy of the passed-in options (keeping
+            // only display-related and launch-root-task information) for these activities.
+            bottomOptions = options.selectiveCloneLaunchOptions();
         }
         try {
             intents = ArrayUtils.filterNotNull(intents, Intent[]::new);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index e3916cb..fe691c6 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2593,6 +2593,9 @@
                         // activity lifecycle transaction to make sure the override pending app
                         // transition will be applied immediately.
                         targetActivity.applyOptionsAnimation();
+                        if (activityOptions != null && activityOptions.getLaunchCookie() != null) {
+                            targetActivity.mLaunchCookie = activityOptions.getLaunchCookie();
+                        }
                     } finally {
                         mActivityMetricsLogger.notifyActivityLaunched(launchingState,
                                 START_TASK_TO_FRONT, false /* newActivityCreated */,
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index e69a732..6d1b5fa 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -117,13 +117,13 @@
 
     /**
      * To ensure that two activities, one using this object, and the other using the
-     * SafeActivityOptions returned from this function, are launched into the same display through
-     * ActivityStartController#startActivities, all display-related information, i.e.
-     * displayAreaToken, launchDisplayId and callerDisplayId, are cloned.
+     * SafeActivityOptions returned from this function, are launched into the same display/root task
+     * through ActivityStartController#startActivities, all display-related information, i.e.
+     * displayAreaToken, launchDisplayId, callerDisplayId and the launch root task are cloned.
      */
-    @Nullable SafeActivityOptions selectiveCloneDisplayOptions() {
-        final ActivityOptions options = cloneLaunchingDisplayOptions(mOriginalOptions);
-        final ActivityOptions callerOptions = cloneLaunchingDisplayOptions(mCallerOptions);
+    @Nullable SafeActivityOptions selectiveCloneLaunchOptions() {
+        final ActivityOptions options = cloneLaunchingOptions(mOriginalOptions);
+        final ActivityOptions callerOptions = cloneLaunchingOptions(mCallerOptions);
         if (options == null && callerOptions == null) {
             return null;
         }
@@ -136,11 +136,12 @@
         return safeOptions;
     }
 
-    private ActivityOptions cloneLaunchingDisplayOptions(ActivityOptions options) {
+    private ActivityOptions cloneLaunchingOptions(ActivityOptions options) {
         return options == null ? null : ActivityOptions.makeBasic()
                 .setLaunchTaskDisplayArea(options.getLaunchTaskDisplayArea())
                 .setLaunchDisplayId(options.getLaunchDisplayId())
-                .setCallerDisplayId((options.getCallerDisplayId()));
+                .setCallerDisplayId(options.getCallerDisplayId())
+                .setLaunchRootTask(options.getLaunchRootTask());
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 731754e..eba49bb 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -21,7 +21,6 @@
 import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
 import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -5855,12 +5854,10 @@
             return false;
         }
 
-        // Existing Tasks can be reused if a new root task will be created anyway, or for the
-        // Dream - because there can only ever be one DreamActivity.
+        // Existing Tasks can be reused if a new root task will be created anyway.
         final int windowingMode = getWindowingMode();
         final int activityType = getActivityType();
-        return DisplayContent.alwaysCreateRootTask(windowingMode, activityType)
-                || activityType == ACTIVITY_TYPE_DREAM;
+        return DisplayContent.alwaysCreateRootTask(windowingMode, activityType);
     }
 
     void addChild(WindowContainer child, final boolean toTop, boolean showForAllUsers) {
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 4a51b83..f53a1cf 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -383,6 +383,7 @@
     </xs:complexType>
 
     <xs:complexType name="autoBrightness">
+        <xs:attribute name="enabled" type="xs:boolean" use="optional" default="true"/>
         <xs:sequence>
             <!-- Sets the debounce for autoBrightness brightening in millis-->
             <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 748ef4b..d89bd7c 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -6,9 +6,11 @@
     method public final java.math.BigInteger getBrighteningLightDebounceMillis();
     method public final java.math.BigInteger getDarkeningLightDebounceMillis();
     method public final com.android.server.display.config.DisplayBrightnessMapping getDisplayBrightnessMapping();
+    method public boolean getEnabled();
     method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
     method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
     method public final void setDisplayBrightnessMapping(com.android.server.display.config.DisplayBrightnessMapping);
+    method public void setEnabled(boolean);
   }
 
   public class BrightnessThresholds {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2ee4af5..4614194 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1823,7 +1823,8 @@
                 t.traceBegin("StartStatusBarManagerService");
                 try {
                     statusBar = new StatusBarManagerService(context);
-                    ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar);
+                    ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar, false,
+                            DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
                 } catch (Throwable e) {
                     reportWtf("starting StatusBarManagerService", e);
                 }
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt
index 45e0d09..4ef6875 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt
@@ -628,6 +628,7 @@
             CodePath.SAME, CodePath.DIFFERENT ->
                 throw AssertionError("secondDataPath cannot be a data path")
             CodePath.SYSTEM -> assertThat(codePaths[1]).isEqualTo(stubFile.parent.toString())
+            else -> {}
         }
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index b9d6b2c..994df22 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -392,7 +392,7 @@
         whenever(rule.mocks().appsFilter.getVisibilityAllowList(
                 any(PackageDataSnapshot::class.java),
             argThat { it?.packageName == pkgSetting.packageName }, any(IntArray::class.java),
-            any() as ArrayMap<String, out PackageStateInternal>
+            any<ArrayMap<String, out PackageStateInternal>>()
         ))
             .thenReturn(list)
     }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index a5c181d..606f486 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -41,6 +41,7 @@
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
 import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -73,6 +74,7 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.time.Clock;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Consumer;
@@ -128,6 +130,8 @@
     private ICancellationSignal mCancellationSignal;
     @Mock
     private Probe mLuxProbe;
+    @Mock
+    private Clock mClock;
     @Captor
     private ArgumentCaptor<OperationContext> mOperationContextCaptor;
     @Captor
@@ -447,6 +451,52 @@
     }
 
     @Test
+    public void sideFingerprintSkipsWindowIfVendorMessageMatch() throws Exception {
+        when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+        final int vendorAcquireMessage = 1234;
+
+        mContext.getOrCreateTestableResources().addOverride(
+                R.integer.config_sidefpsSkipWaitForPowerAcquireMessage,
+                FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR);
+        mContext.getOrCreateTestableResources().addOverride(
+                R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage,
+                vendorAcquireMessage);
+
+        final FingerprintAuthenticationClient client = createClient(1);
+        client.start(mCallback);
+        mLooper.dispatchAll();
+        client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
+                true /* authenticated */, new ArrayList<>());
+        client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, vendorAcquireMessage);
+        mLooper.dispatchAll();
+
+        verify(mCallback).onClientFinished(any(), eq(true));
+    }
+
+    @Test
+    public void sideFingerprintDoesNotSkipWindowOnVendorErrorMismatch() throws Exception {
+        when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+        final int vendorAcquireMessage = 1234;
+
+        mContext.getOrCreateTestableResources().addOverride(
+                R.integer.config_sidefpsSkipWaitForPowerAcquireMessage,
+                FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR);
+        mContext.getOrCreateTestableResources().addOverride(
+                R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage,
+                vendorAcquireMessage);
+
+        final FingerprintAuthenticationClient client = createClient(1);
+        client.start(mCallback);
+        mLooper.dispatchAll();
+        client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
+                true /* authenticated */, new ArrayList<>());
+        client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, 1);
+        mLooper.dispatchAll();
+
+        verify(mCallback, never()).onClientFinished(any(), anyBoolean());
+    }
+
+    @Test
     public void sideFingerprintSendsAuthIfFingerUp() throws Exception {
         when(mSensorProps.isAnySidefpsType()).thenReturn(true);
 
@@ -493,6 +543,79 @@
         verify(mCallback).onClientFinished(any(), eq(true));
     }
 
+    @Test
+    public void sideFingerprintPowerWindowStartsOnAcquireStart() throws Exception {
+        final int powerWindow = 500;
+        final long authStart = 300;
+
+        when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+        mContext.getOrCreateTestableResources().addOverride(
+                R.integer.config_sidefpsBpPowerPressWindow, powerWindow);
+
+        final FingerprintAuthenticationClient client = createClient(1);
+        client.start(mCallback);
+
+        // Acquire start occurs at time = 0ms
+        when(mClock.millis()).thenReturn(0L);
+        client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */);
+
+        // Auth occurs at time = 300
+        when(mClock.millis()).thenReturn(authStart);
+        // At this point the delay should be 500 - (300 - 0) == 200 milliseconds.
+        client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
+                true /* authenticated */, new ArrayList<>());
+        mLooper.dispatchAll();
+        verify(mCallback, never()).onClientFinished(any(), anyBoolean());
+
+        // After waiting 200 milliseconds, auth should succeed.
+        mLooper.moveTimeForward(powerWindow - authStart);
+        mLooper.dispatchAll();
+        verify(mCallback).onClientFinished(any(), eq(true));
+    }
+
+    @Test
+    public void sideFingerprintPowerWindowStartsOnLastAcquireStart() throws Exception {
+        final int powerWindow = 500;
+
+        when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+        mContext.getOrCreateTestableResources().addOverride(
+                R.integer.config_sidefpsBpPowerPressWindow, powerWindow);
+
+        final FingerprintAuthenticationClient client = createClient(1);
+        client.start(mCallback);
+        // Acquire start occurs at time = 0ms
+        when(mClock.millis()).thenReturn(0L);
+        client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */);
+
+        // Auth reject occurs at time = 300ms
+        when(mClock.millis()).thenReturn(300L);
+        client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
+                false /* authenticated */, new ArrayList<>());
+        mLooper.dispatchAll();
+
+        mLooper.moveTimeForward(300);
+        mLooper.dispatchAll();
+        verify(mCallback, never()).onClientFinished(any(), anyBoolean());
+
+        when(mClock.millis()).thenReturn(1300L);
+        client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */);
+
+        // If code is correct, the new acquired start timestamp should be used
+        // and the code should only have to wait 500 - (1500-1300)ms.
+        when(mClock.millis()).thenReturn(1500L);
+        client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
+                true /* authenticated */, new ArrayList<>());
+        mLooper.dispatchAll();
+
+        mLooper.moveTimeForward(299);
+        mLooper.dispatchAll();
+        verify(mCallback, never()).onClientFinished(any(), anyBoolean());
+
+        mLooper.moveTimeForward(1);
+        mLooper.dispatchAll();
+        verify(mCallback).onClientFinished(any(), eq(true));
+    }
+
     private FingerprintAuthenticationClient createClient() throws RemoteException {
         return createClient(100 /* version */, true /* allowBackgroundAuthentication */);
     }
@@ -520,7 +643,7 @@
                 null /* taskStackListener */, mLockoutCache,
                 mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication,
                 mSensorProps,
-                new Handler(mLooper.getLooper())) {
+                new Handler(mLooper.getLooper()), mClock) {
             @Override
             protected ActivityTaskManager getActivityTaskManager() {
                 return mActivityTaskManager;
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index 356600d..0642228 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -885,6 +886,29 @@
         assertNull(mInjector.mLightSensor);
     }
 
+    @Test
+    public void testOnlyOneReceiverRegistered() {
+        assertNull(mInjector.mLightSensor);
+        assertNull(mInjector.mSensorListener);
+        startTracker(mTracker, 0.3f, false);
+
+        assertNotNull(mInjector.mLightSensor);
+        assertNotNull(mInjector.mSensorListener);
+        Sensor registeredLightSensor = mInjector.mLightSensor;
+        SensorEventListener registeredSensorListener = mInjector.mSensorListener;
+
+        mTracker.start(0.3f);
+        assertSame(registeredLightSensor, mInjector.mLightSensor);
+        assertSame(registeredSensorListener, mInjector.mSensorListener);
+
+        mTracker.stop();
+        assertNull(mInjector.mLightSensor);
+        assertNull(mInjector.mSensorListener);
+
+        // mInjector asserts that we aren't removing a null receiver
+        mTracker.stop();
+    }
+
     private InputStream getInputStream(String data) {
         return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
     }
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index a719f52..30024fb 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -49,6 +49,13 @@
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public final class DisplayDeviceConfigTest {
+    private static final int DEFAULT_PEAK_REFRESH_RATE = 75;
+    private static final int DEFAULT_REFRESH_RATE = 120;
+    private static final int[] LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{10, 30};
+    private static final int[] LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{1, 21};
+    private static final int[] HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{160};
+    private static final int[] HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{30000};
+
     private DisplayDeviceConfig mDisplayDeviceConfig;
     private static final float ZERO_DELTA = 0.0f;
     private static final float SMALL_DELTA = 0.0001f;
@@ -204,6 +211,16 @@
         assertArrayEquals(new float[]{29, 30, 31},
                 mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
 
+        assertEquals(mDisplayDeviceConfig.getDefaultRefreshRate(), DEFAULT_REFRESH_RATE);
+        assertEquals(mDisplayDeviceConfig.getDefaultPeakRefreshRate(), DEFAULT_PEAK_REFRESH_RATE);
+        assertArrayEquals(mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(),
+                LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
+        assertArrayEquals(mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(),
+                LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
+        assertArrayEquals(mDisplayDeviceConfig.getHighDisplayBrightnessThresholds(),
+                HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
+        assertArrayEquals(mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(),
+                HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
         // Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
         // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
     }
@@ -465,6 +482,21 @@
         when(mResources.getIntArray(R.array.config_screenDarkeningThresholds))
                 .thenReturn(new int[]{370, 380, 390});
 
+        // Configs related to refresh rates and blocking zones
+        when(mResources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate))
+                .thenReturn(DEFAULT_PEAK_REFRESH_RATE);
+        when(mResources.getInteger(com.android.internal.R.integer.config_defaultRefreshRate))
+                .thenReturn(DEFAULT_REFRESH_RATE);
+        when(mResources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
+                .thenReturn(LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
+        when(mResources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
+                .thenReturn(LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
+        when(mResources.getIntArray(
+                R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+                .thenReturn(HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
+        when(mResources.getIntArray(
+                R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+                .thenReturn(HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
         mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 968e1d8..18dd264 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -1853,6 +1853,20 @@
         assertNull(vote);
     }
 
+    @Test
+    public void testNotifyDefaultDisplayDeviceUpdated() {
+        DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class);
+        when(displayDeviceConfig.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{});
+        when(displayDeviceConfig.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{});
+        when(displayDeviceConfig.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{});
+        when(displayDeviceConfig.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{});
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        director.defaultDisplayDeviceUpdated(displayDeviceConfig);
+        verify(displayDeviceConfig).getDefaultRefreshRate();
+        verify(displayDeviceConfig).getDefaultPeakRefreshRate();
+    }
+
     private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
         return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
     }
diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
index 9fe8609c..3b0a22f 100644
--- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -275,6 +275,75 @@
         assertNull(mDataStore.getBrightnessConfiguration(userSerial));
     }
 
+    @Test
+    public void testStoreAndRestoreResolution() {
+        final String uniqueDisplayId = "test:123";
+        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+            @Override
+            public boolean hasStableUniqueId() {
+                return true;
+            }
+
+            @Override
+            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+                return null;
+            }
+        };
+        int width = 35;
+        int height = 45;
+        mDataStore.loadIfNeeded();
+        mDataStore.setUserPreferredResolution(testDisplayDevice, width, height);
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mInjector.setWriteStream(baos);
+        mDataStore.saveIfNeeded();
+        mTestLooper.dispatchAll();
+        assertTrue(mInjector.wasWriteSuccessful());
+        TestInjector newInjector = new TestInjector();
+        PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        newInjector.setReadStream(bais);
+        newDataStore.loadIfNeeded();
+        assertNotNull(newDataStore.getUserPreferredResolution(testDisplayDevice));
+        assertEquals(35, newDataStore.getUserPreferredResolution(testDisplayDevice).x);
+        assertEquals(35, mDataStore.getUserPreferredResolution(testDisplayDevice).x);
+        assertEquals(45, newDataStore.getUserPreferredResolution(testDisplayDevice).y);
+        assertEquals(45, mDataStore.getUserPreferredResolution(testDisplayDevice).y);
+    }
+
+    @Test
+    public void testStoreAndRestoreRefreshRate() {
+        final String uniqueDisplayId = "test:123";
+        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+            @Override
+            public boolean hasStableUniqueId() {
+                return true;
+            }
+
+            @Override
+            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+                return null;
+            }
+        };
+        float refreshRate = 85.3f;
+        mDataStore.loadIfNeeded();
+        mDataStore.setUserPreferredRefreshRate(testDisplayDevice, refreshRate);
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mInjector.setWriteStream(baos);
+        mDataStore.saveIfNeeded();
+        mTestLooper.dispatchAll();
+        assertTrue(mInjector.wasWriteSuccessful());
+        TestInjector newInjector = new TestInjector();
+        PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        newInjector.setReadStream(bais);
+        newDataStore.loadIfNeeded();
+        assertNotNull(newDataStore.getUserPreferredRefreshRate(testDisplayDevice));
+        assertEquals(85.3f, mDataStore.getUserPreferredRefreshRate(testDisplayDevice), 01.f);
+        assertEquals(85.3f, newDataStore.getUserPreferredRefreshRate(testDisplayDevice), 0.1f);
+    }
+
     public class TestInjector extends PersistentDataStore.Injector {
         private InputStream mReadStream;
         private OutputStream mWriteStream;
diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java
new file mode 100644
index 0000000..303a370
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.server.dreams;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.service.dreams.IDreamService;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+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;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DreamControllerTest {
+    @Mock
+    private DreamController.Listener mListener;
+    @Mock
+    private Context mContext;
+    @Mock
+    private IBinder mIBinder;
+    @Mock
+    private IDreamService mIDreamService;
+
+    @Captor
+    private ArgumentCaptor<ServiceConnection> mServiceConnectionACaptor;
+    @Captor
+    private ArgumentCaptor<IRemoteCallback> mRemoteCallbackCaptor;
+
+    private final TestLooper mLooper = new TestLooper();
+    private final Handler mHandler = new Handler(mLooper.getLooper());
+
+    private DreamController mDreamController;
+
+    private Binder mToken;
+    private ComponentName mDreamName;
+    private ComponentName mOverlayName;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mIDreamService.asBinder()).thenReturn(mIBinder);
+        when(mIBinder.queryLocalInterface(anyString())).thenReturn(mIDreamService);
+        when(mContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
+
+        mToken = new Binder();
+        mDreamName = ComponentName.unflattenFromString("dream");
+        mOverlayName = ComponentName.unflattenFromString("dream_overlay");
+        mDreamController = new DreamController(mContext, mHandler, mListener);
+    }
+
+    @Test
+    public void startDream_attachOnServiceConnected() throws RemoteException {
+        // Call dream controller to start dreaming.
+        mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
+                0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
+
+        // Mock service connected.
+        final ServiceConnection serviceConnection = captureServiceConnection();
+        serviceConnection.onServiceConnected(mDreamName, mIBinder);
+        mLooper.dispatchAll();
+
+        // Verify that dream service is called to attach.
+        verify(mIDreamService).attach(eq(mToken), eq(false) /*doze*/, any());
+    }
+
+    @Test
+    public void startDream_startASecondDream_detachOldDreamOnceNewDreamIsStarted()
+            throws RemoteException {
+        // Start first dream.
+        mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
+                0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
+        captureServiceConnection().onServiceConnected(mDreamName, mIBinder);
+        mLooper.dispatchAll();
+        clearInvocations(mContext);
+
+        // Set up second dream.
+        final Binder newToken = new Binder();
+        final ComponentName newDreamName = ComponentName.unflattenFromString("new_dream");
+        final ComponentName newOverlayName = ComponentName.unflattenFromString("new_dream_overlay");
+        final IDreamService newDreamService = mock(IDreamService.class);
+        final IBinder newBinder = mock(IBinder.class);
+        when(newDreamService.asBinder()).thenReturn(newBinder);
+        when(newBinder.queryLocalInterface(anyString())).thenReturn(newDreamService);
+
+        // Start second dream.
+        mDreamController.startDream(newToken, newDreamName, false /*isPreview*/, false /*doze*/,
+                0 /*userId*/, null /*wakeLock*/, newOverlayName, "test" /*reason*/);
+        captureServiceConnection().onServiceConnected(newDreamName, newBinder);
+        mLooper.dispatchAll();
+
+        // Mock second dream started.
+        verify(newDreamService).attach(eq(newToken), eq(false) /*doze*/,
+                mRemoteCallbackCaptor.capture());
+        mRemoteCallbackCaptor.getValue().sendResult(null /*data*/);
+        mLooper.dispatchAll();
+
+        // Verify that the first dream is called to detach.
+        verify(mIDreamService).detach();
+    }
+
+    @Test
+    public void stopDream_detachFromService() throws RemoteException {
+        // Start dream.
+        mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
+                0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
+        captureServiceConnection().onServiceConnected(mDreamName, mIBinder);
+        mLooper.dispatchAll();
+
+        // Stop dream.
+        mDreamController.stopDream(true /*immediate*/, "test stop dream" /*reason*/);
+
+        // Verify that dream service is called to detach.
+        verify(mIDreamService).detach();
+    }
+
+    private ServiceConnection captureServiceConnection() {
+        verify(mContext).bindServiceAsUser(any(), mServiceConnectionACaptor.capture(), anyInt(),
+                any());
+        return mServiceConnectionACaptor.getValue();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
index d8c9c34..e3ca170 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
@@ -116,7 +116,7 @@
     @Test
     public void testDreamPowerGroup() {
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        mPowerGroup.dreamLocked(TIMESTAMP1, UID);
+        mPowerGroup.dreamLocked(TIMESTAMP1, UID, /* allowWake= */ false);
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
         assertThat(mPowerGroup.isSandmanSummonedLocked()).isTrue();
         verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
@@ -172,7 +172,7 @@
                 eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ isNull(),
                 /* details= */ isNull());
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
-        assertThat(mPowerGroup.dreamLocked(TIMESTAMP2, UID)).isFalse();
+        assertThat(mPowerGroup.dreamLocked(TIMESTAMP2, UID, /* allowWake= */ false)).isFalse();
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
         verify(mWakefulnessCallbackMock, never()).onWakefulnessChangedLocked(
                 eq(GROUP_ID), /* wakefulness= */ eq(WAKEFULNESS_DREAMING), eq(TIMESTAMP2),
@@ -181,6 +181,22 @@
     }
 
     @Test
+    public void testDreamPowerGroupWhenNotAwakeShouldWake() {
+        mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_TIMEOUT);
+        verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
+                eq(WAKEFULNESS_DOZING), eq(TIMESTAMP1), eq(GO_TO_SLEEP_REASON_TIMEOUT),
+                eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ isNull(),
+                /* details= */ isNull());
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        assertThat(mPowerGroup.dreamLocked(TIMESTAMP2, UID, /* allowWake= */ true)).isTrue();
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+        verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(
+                eq(GROUP_ID), /* wakefulness= */ eq(WAKEFULNESS_DREAMING), eq(TIMESTAMP2),
+                /* reason= */ anyInt(), eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ any(),
+                /* details= */ any());
+    }
+
+    @Test
     public void testLastWakeAndSleepTimeIsUpdated() {
         assertThat(mPowerGroup.getLastWakeTimeLocked()).isEqualTo(TIMESTAMP_CREATE);
         assertThat(mPowerGroup.getLastSleepTimeLocked()).isEqualTo(TIMESTAMP_CREATE);
@@ -514,7 +530,7 @@
                 .setBatterySaverEnabled(batterySaverEnabled)
                 .setBrightnessFactor(brightnessFactor)
                 .build();
-        mPowerGroup.dreamLocked(TIMESTAMP1, UID);
+        mPowerGroup.dreamLocked(TIMESTAMP1, UID, /* allowWake= */ false);
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
         mPowerGroup.setWakeLockSummaryLocked(WAKE_LOCK_SCREEN_BRIGHT);
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 2a6e6d8..f5ed41a 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -393,6 +393,12 @@
                 .thenReturn(minimumScreenOffTimeoutConfigMillis);
     }
 
+    private void setDreamsDisabledByAmbientModeSuppressionConfig(boolean disable) {
+        when(mResourcesSpy.getBoolean(
+                com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig))
+                .thenReturn(disable);
+    }
+
     private void advanceTime(long timeMs) {
         mClock.fastForward(timeMs);
         mTestLooper.dispatchAll();
@@ -612,6 +618,31 @@
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
     }
 
+    /**
+     * Tests that dreaming continues when undocking and configured to do so.
+     */
+    @Test
+    public void testWakefulnessDream_shouldKeepDreamingWhenUndocked() {
+        createService();
+        startSystem();
+
+        when(mResourcesSpy.getBoolean(
+                com.android.internal.R.bool.config_keepDreamingWhenUndocking))
+                .thenReturn(true);
+        mService.readConfigurationLocked();
+
+        when(mBatteryManagerInternalMock.getPlugType())
+                .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
+        setPluggedIn(true);
+
+        forceAwake();  // Needs to be awake first before it can dream.
+        forceDream();
+        when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+        setPluggedIn(false);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+    }
+
     @Test
     public void testWakefulnessDoze_goToSleep() {
         createService();
@@ -765,6 +796,91 @@
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
     }
 
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testAmbientSuppression_disablesDreamingAndWakesDevice() {
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ENABLED, 1);
+
+        setDreamsDisabledByAmbientModeSuppressionConfig(true);
+        setMinimumScreenOffTimeoutConfig(10000);
+        createService();
+        startSystem();
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        setPluggedIn(true);
+        // Allow asynchronous sandman calls to execute.
+        advanceTime(10000);
+
+        forceDream();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+        advanceTime(50);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testAmbientSuppressionDisabled_shouldNotWakeDevice() {
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ENABLED, 1);
+
+        setDreamsDisabledByAmbientModeSuppressionConfig(false);
+        setMinimumScreenOffTimeoutConfig(10000);
+        createService();
+        startSystem();
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        setPluggedIn(true);
+        // Allow asynchronous sandman calls to execute.
+        advanceTime(10000);
+
+        forceDream();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+        advanceTime(50);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+    }
+
+    @Test
+    public void testAmbientSuppression_doesNotAffectDreamForcing() {
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ENABLED, 1);
+
+        setDreamsDisabledByAmbientModeSuppressionConfig(true);
+        setMinimumScreenOffTimeoutConfig(10000);
+        createService();
+        startSystem();
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+        setPluggedIn(true);
+        // Allow asynchronous sandman calls to execute.
+        advanceTime(10000);
+
+        // Verify that forcing dream still works even though ambient display is suppressed
+        forceDream();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+    }
+
     @Test
     public void testSetDozeOverrideFromDreamManager_triggersSuspendBlocker() {
         final String suspendBlockerName = "PowerManagerService.Display";
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
index 235849c..c484f45 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -53,7 +53,8 @@
 
     private boolean mIsAvailable = true;
     private boolean mIsInfoLoadSuccessful = true;
-    private long mLatency;
+    private long mOnLatency;
+    private long mOffLatency;
     private int mOffCount;
 
     private int mCapabilities;
@@ -97,7 +98,7 @@
         public long on(long milliseconds, long vibrationId) {
             recordEffectSegment(vibrationId, new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE,
                     /* frequencyHz= */ 0, (int) milliseconds));
-            applyLatency();
+            applyLatency(mOnLatency);
             scheduleListener(milliseconds, vibrationId);
             return milliseconds;
         }
@@ -105,12 +106,13 @@
         @Override
         public void off() {
             mOffCount++;
+            applyLatency(mOffLatency);
         }
 
         @Override
         public void setAmplitude(float amplitude) {
             mAmplitudes.add(amplitude);
-            applyLatency();
+            applyLatency(mOnLatency);
         }
 
         @Override
@@ -121,7 +123,7 @@
             }
             recordEffectSegment(vibrationId,
                     new PrebakedSegment((int) effect, false, (int) strength));
-            applyLatency();
+            applyLatency(mOnLatency);
             scheduleListener(EFFECT_DURATION, vibrationId);
             return EFFECT_DURATION;
         }
@@ -141,7 +143,7 @@
                 duration += EFFECT_DURATION + primitive.getDelay();
                 recordEffectSegment(vibrationId, primitive);
             }
-            applyLatency();
+            applyLatency(mOnLatency);
             scheduleListener(duration, vibrationId);
             return duration;
         }
@@ -154,7 +156,7 @@
                 recordEffectSegment(vibrationId, primitive);
             }
             recordBraking(vibrationId, braking);
-            applyLatency();
+            applyLatency(mOnLatency);
             scheduleListener(duration, vibrationId);
             return duration;
         }
@@ -193,10 +195,10 @@
             return mIsInfoLoadSuccessful;
         }
 
-        private void applyLatency() {
+        private void applyLatency(long latencyMillis) {
             try {
-                if (mLatency > 0) {
-                    Thread.sleep(mLatency);
+                if (latencyMillis > 0) {
+                    Thread.sleep(latencyMillis);
                 }
             } catch (InterruptedException e) {
             }
@@ -240,10 +242,15 @@
 
     /**
      * Sets the latency this controller should fake for turning the vibrator hardware on or setting
-     * it's vibration amplitude.
+     * the vibration amplitude.
      */
-    public void setLatency(long millis) {
-        mLatency = millis;
+    public void setOnLatency(long millis) {
+        mOnLatency = millis;
+    }
+
+    /** Sets the latency this controller should fake for turning the vibrator off. */
+    public void setOffLatency(long millis) {
+        mOffLatency = millis;
     }
 
     /** Set the capabilities of the fake vibrator hardware. */
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index 0551bfc..42a2c10 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -1118,7 +1118,7 @@
 
         // 25% of the first waveform step will be spent on the native on() call.
         // 25% of each waveform step will be spent on the native setAmplitude() call..
-        mVibratorProviders.get(VIBRATOR_ID).setLatency(stepDuration / 4);
+        mVibratorProviders.get(VIBRATOR_ID).setOnLatency(stepDuration / 4);
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
 
         int stepCount = totalDuration / stepDuration;
@@ -1149,7 +1149,7 @@
         fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
 
         long latency = 5_000; // 5s
-        fakeVibrator.setLatency(latency);
+        fakeVibrator.setOnLatency(latency);
 
         long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
@@ -1163,8 +1163,7 @@
         // fail at waitForCompletion(cancellingThread).
         Thread cancellingThread = new Thread(
                 () -> conductor.notifyCancelled(
-                        new Vibration.EndInfo(
-                                Vibration.Status.CANCELLED_BY_USER),
+                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
                         /* immediate= */ false));
         cancellingThread.start();
 
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index fe0a79c..039e159 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -817,6 +817,39 @@
         verify(mBatteryStatsMock).noteVibratorOn(UID, 5000);
         // The second vibration shouldn't have recorded that the vibrators were turned on.
         verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
+        // No segment played is the prebaked CLICK from the second vibration.
+        assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
+                .anyMatch(PrebakedSegment.class::isInstance));
+        // Clean up repeating effect.
+        service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
+    }
+
+    @Test
+    public void vibrate_withOngoingRepeatingVibrationBeingCancelled_playsAfterPreviousIsCancelled()
+            throws Exception {
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setOffLatency(50); // Add latency so cancellation is slow.
+        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        VibratorManagerService service = createSystemReadyService();
+
+        VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
+                new long[]{10, 10_000}, new int[]{255, 0}, 1);
+        vibrate(service, repeatingEffect, ALARM_ATTRS);
+
+        // VibrationThread will start this vibration async, wait until the off waveform step.
+        assertTrue(waitUntil(s -> fakeVibrator.getOffCount() > 0, service, TEST_TIMEOUT_MILLIS));
+
+        // Cancel vibration right before requesting a new one.
+        // This should trigger slow IVibrator.off before setting the vibration status to cancelled.
+        service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                ALARM_ATTRS);
+
+        // Check that second vibration was played.
+        assertTrue(fakeVibrator.getAllEffectSegments().stream()
+                .anyMatch(PrebakedSegment.class::isInstance));
     }
 
     @Test
@@ -867,6 +900,11 @@
 
         // The second vibration shouldn't have recorded that the vibrators were turned on.
         verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
+        // The second vibration shouldn't have played any prebaked segment.
+        assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
+                .anyMatch(PrebakedSegment.class::isInstance));
+        // Clean up long effect.
+        service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 91c2fe0..8e81e2d 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -1371,6 +1371,39 @@
         verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
     }
 
+    @Test
+    public void dreamWhenDocked_ambientModeSuppressed_suppressionEnabled() {
+        mUiManagerService.setStartDreamImmediatelyOnDock(true);
+        mUiManagerService.setDreamsDisabledByAmbientModeSuppression(true);
+
+        when(mLocalPowerManager.isAmbientDisplaySuppressed()).thenReturn(true);
+        triggerDockIntent();
+        verifyAndSendResultBroadcast();
+        verify(mInjector, never()).startDreamWhenDockedIfAppropriate(mContext);
+    }
+
+    @Test
+    public void dreamWhenDocked_ambientModeSuppressed_suppressionDisabled() {
+        mUiManagerService.setStartDreamImmediatelyOnDock(true);
+        mUiManagerService.setDreamsDisabledByAmbientModeSuppression(false);
+
+        when(mLocalPowerManager.isAmbientDisplaySuppressed()).thenReturn(true);
+        triggerDockIntent();
+        verifyAndSendResultBroadcast();
+        verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
+    }
+
+    @Test
+    public void dreamWhenDocked_ambientModeNotSuppressed_suppressionEnabled() {
+        mUiManagerService.setStartDreamImmediatelyOnDock(true);
+        mUiManagerService.setDreamsDisabledByAmbientModeSuppression(true);
+
+        when(mLocalPowerManager.isAmbientDisplaySuppressed()).thenReturn(false);
+        triggerDockIntent();
+        verifyAndSendResultBroadcast();
+        verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
+    }
+
     private void triggerDockIntent() {
         final Intent dockedIntent =
                 new Intent(Intent.ACTION_DOCK_EVENT)
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index c5131c8..101a5cb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.notification;
 
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
@@ -30,9 +31,11 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.intThat;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -49,6 +52,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.content.pm.VersionedPackage;
+import android.content.res.Resources;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerFilter;
@@ -69,6 +73,7 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.internal.util.reflection.FieldSetter;
@@ -77,6 +82,7 @@
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.util.Arrays;
 import java.util.List;
 
 public class NotificationListenersTest extends UiServiceTestCase {
@@ -85,6 +91,8 @@
     private PackageManager mPm;
     @Mock
     private IPackageManager miPm;
+    @Mock
+    private Resources mResources;
 
     @Mock
     NotificationManagerService mNm;
@@ -96,7 +104,8 @@
 
     private ComponentName mCn1 = new ComponentName("pkg", "pkg.cmp");
     private ComponentName mCn2 = new ComponentName("pkg2", "pkg2.cmp2");
-
+    private ComponentName mUninstalledComponent = new ComponentName("pkg3",
+            "pkg3.NotificationListenerService");
 
     @Before
     public void setUp() throws Exception {
@@ -111,7 +120,7 @@
 
     @Test
     public void testReadExtraTag() throws Exception {
-        String xml = "<" + TAG_REQUESTED_LISTENERS+ ">"
+        String xml = "<" + TAG_REQUESTED_LISTENERS + ">"
                 + "<listener component=\"" + mCn1.flattenToString() + "\" user=\"0\">"
                 + "<allowed types=\"7\" />"
                 + "</listener>"
@@ -131,11 +140,55 @@
     }
 
     @Test
+    public void loadDefaultsFromConfig_forHeadlessSystemUser_loadUninstalled() throws Exception {
+        // setup with headless system user mode
+        mListeners = spy(mNm.new NotificationListeners(
+                mContext, new Object(), mock(ManagedServices.UserProfiles.class), miPm,
+                /* isHeadlessSystemUserMode= */ true));
+        mockDefaultListenerConfigForUninstalledComponent(mUninstalledComponent);
+
+        mListeners.loadDefaultsFromConfig();
+
+        assertThat(mListeners.getDefaultComponents()).contains(mUninstalledComponent);
+    }
+
+    @Test
+    public void loadDefaultsFromConfig_forNonHeadlessSystemUser_ignoreUninstalled()
+            throws Exception {
+        // setup without headless system user mode
+        mListeners = spy(mNm.new NotificationListeners(
+                mContext, new Object(), mock(ManagedServices.UserProfiles.class), miPm,
+                /* isHeadlessSystemUserMode= */ false));
+        mockDefaultListenerConfigForUninstalledComponent(mUninstalledComponent);
+
+        mListeners.loadDefaultsFromConfig();
+
+        assertThat(mListeners.getDefaultComponents()).doesNotContain(mUninstalledComponent);
+    }
+
+    private void mockDefaultListenerConfigForUninstalledComponent(ComponentName componentName) {
+        ArraySet<ComponentName> components = new ArraySet<>(Arrays.asList(componentName));
+        when(mResources
+                .getString(
+                        com.android.internal.R.string.config_defaultListenerAccessPackages))
+                .thenReturn(componentName.getPackageName());
+        when(mContext.getResources()).thenReturn(mResources);
+        doReturn(components).when(mListeners).queryPackageForServices(
+                eq(componentName.getPackageName()),
+                intThat(hasIntBitFlag(MATCH_ANY_USER)),
+                anyInt());
+    }
+
+    public static ArgumentMatcher<Integer> hasIntBitFlag(int flag) {
+        return arg -> arg != null && ((arg & flag) == flag);
+    }
+
+    @Test
     public void testWriteExtraTag() throws Exception {
         NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
         VersionedPackage a1 = new VersionedPackage("pkg1", 243);
         NotificationListenerFilter nlf2 =
-                new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
+                new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[]{a1}));
         mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
         mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2);
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java
deleted file mode 100644
index d765042..0000000
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java
+++ /dev/null
@@ -1,551 +0,0 @@
-/*
- * Copyright (C) 2017 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.server.notification;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.Person;
-import android.app.RemoteInput;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Typeface;
-import android.graphics.drawable.Icon;
-import android.net.Uri;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.style.StyleSpan;
-import android.util.Pair;
-import android.widget.RemoteViews;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.UiServiceTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class NotificationTest extends UiServiceTestCase {
-
-    @Mock
-    ActivityManager mAm;
-
-    @Mock
-    Resources mResources;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Test
-    public void testDoesNotStripsExtenders() {
-        Notification.Builder nb = new Notification.Builder(mContext, "channel");
-        nb.extend(new Notification.CarExtender().setColor(Color.RED));
-        nb.extend(new Notification.TvExtender().setChannelId("different channel"));
-        nb.extend(new Notification.WearableExtender().setDismissalId("dismiss"));
-        Notification before = nb.build();
-        Notification after = Notification.Builder.maybeCloneStrippedForDelivery(before);
-
-        assertTrue(before == after);
-
-        assertEquals("different channel", new Notification.TvExtender(before).getChannelId());
-        assertEquals(Color.RED, new Notification.CarExtender(before).getColor());
-        assertEquals("dismiss", new Notification.WearableExtender(before).getDismissalId());
-    }
-
-    @Test
-    public void testStyleChangeVisiblyDifferent_noStyles() {
-        Notification.Builder n1 = new Notification.Builder(mContext, "test");
-        Notification.Builder n2 = new Notification.Builder(mContext, "test");
-
-        assertFalse(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testStyleChangeVisiblyDifferent_noStyleToStyle() {
-        Notification.Builder n1 = new Notification.Builder(mContext, "test");
-        Notification.Builder n2 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.BigTextStyle());
-
-        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testStyleChangeVisiblyDifferent_styleToNoStyle() {
-        Notification.Builder n2 = new Notification.Builder(mContext, "test");
-        Notification.Builder n1 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.BigTextStyle());
-
-        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testStyleChangeVisiblyDifferent_changeStyle() {
-        Notification.Builder n1 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.InboxStyle());
-        Notification.Builder n2 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.BigTextStyle());
-
-        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testInboxTextChange() {
-        Notification.Builder nInbox1 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.InboxStyle().addLine("a").addLine("b"));
-        Notification.Builder nInbox2 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.InboxStyle().addLine("b").addLine("c"));
-
-        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nInbox1, nInbox2));
-    }
-
-    @Test
-    public void testBigTextTextChange() {
-        Notification.Builder nBigText1 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.BigTextStyle().bigText("something"));
-        Notification.Builder nBigText2 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.BigTextStyle().bigText("else"));
-
-        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigText1, nBigText2));
-    }
-
-    @Test
-    public void testBigPictureChange() {
-        Bitmap bitA = mock(Bitmap.class);
-        when(bitA.getGenerationId()).thenReturn(100);
-        Bitmap bitB = mock(Bitmap.class);
-        when(bitB.getGenerationId()).thenReturn(200);
-
-        Notification.Builder nBigPic1 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.BigPictureStyle().bigPicture(bitA));
-        Notification.Builder nBigPic2 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.BigPictureStyle().bigPicture(bitB));
-
-        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigPic1, nBigPic2));
-    }
-
-    @Test
-    public void testMessagingChange_text() {
-        Notification.Builder nM1 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle("")
-                        .addMessage(new Notification.MessagingStyle.Message(
-                                "a", 100, mock(Person.class))));
-        Notification.Builder nM2 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle("")
-                        .addMessage(new Notification.MessagingStyle.Message(
-                                "a", 100, mock(Person.class)))
-                        .addMessage(new Notification.MessagingStyle.Message(
-                                "b", 100, mock(Person.class)))
-                );
-
-        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
-    }
-
-    @Test
-    public void testMessagingChange_data() {
-        Notification.Builder nM1 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle("")
-                        .addMessage(new Notification.MessagingStyle.Message(
-                                "a", 100, mock(Person.class))
-                                .setData("text", mock(Uri.class))));
-        Notification.Builder nM2 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle("")
-                        .addMessage(new Notification.MessagingStyle.Message(
-                                "a", 100, mock(Person.class))));
-
-        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
-    }
-
-    @Test
-    public void testMessagingChange_sender() {
-        Person a = mock(Person.class);
-        when(a.getName()).thenReturn("A");
-        Person b = mock(Person.class);
-        when(b.getName()).thenReturn("b");
-        Notification.Builder nM1 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle("")
-                        .addMessage(new Notification.MessagingStyle.Message("a", 100, b)));
-        Notification.Builder nM2 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle("")
-                        .addMessage(new Notification.MessagingStyle.Message("a", 100, a)));
-
-        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
-    }
-
-    @Test
-    public void testMessagingChange_key() {
-        Person a = mock(Person.class);
-        when(a.getKey()).thenReturn("A");
-        Person b = mock(Person.class);
-        when(b.getKey()).thenReturn("b");
-        Notification.Builder nM1 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle("")
-                        .addMessage(new Notification.MessagingStyle.Message("a", 100, a)));
-        Notification.Builder nM2 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle("")
-                        .addMessage(new Notification.MessagingStyle.Message("a", 100, b)));
-
-        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
-    }
-
-    @Test
-    public void testMessagingChange_ignoreTimeChange() {
-        Notification.Builder nM1 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle("")
-                        .addMessage(new Notification.MessagingStyle.Message(
-                                "a", 100, mock(Person.class))));
-        Notification.Builder nM2 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle("")
-                        .addMessage(new Notification.MessagingStyle.Message(
-                                "a", 1000, mock(Person.class)))
-                );
-
-        assertFalse(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
-    }
-
-    @Test
-    public void testRemoteViews_nullChange() {
-        Notification.Builder n1 = new Notification.Builder(mContext, "test")
-                .setContent(mock(RemoteViews.class));
-        Notification.Builder n2 = new Notification.Builder(mContext, "test");
-        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test");
-        n2 = new Notification.Builder(mContext, "test")
-                .setContent(mock(RemoteViews.class));
-        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test")
-                .setCustomBigContentView(mock(RemoteViews.class));
-        n2 = new Notification.Builder(mContext, "test");
-        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test");
-        n2 = new Notification.Builder(mContext, "test")
-                .setCustomBigContentView(mock(RemoteViews.class));
-        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test");
-        n2 = new Notification.Builder(mContext, "test");
-        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-    }
-
-    @Test
-    public void testRemoteViews_layoutChange() {
-        RemoteViews a = mock(RemoteViews.class);
-        when(a.getLayoutId()).thenReturn(234);
-        RemoteViews b = mock(RemoteViews.class);
-        when(b.getLayoutId()).thenReturn(189);
-
-        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
-        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
-        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
-        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
-        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
-        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
-        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-    }
-
-    @Test
-    public void testRemoteViews_layoutSame() {
-        RemoteViews a = mock(RemoteViews.class);
-        when(a.getLayoutId()).thenReturn(234);
-        RemoteViews b = mock(RemoteViews.class);
-        when(b.getLayoutId()).thenReturn(234);
-
-        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
-        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
-        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
-        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
-        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
-        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
-        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-    }
-
-    @Test
-    public void testRemoteViews_sequenceChange() {
-        RemoteViews a = mock(RemoteViews.class);
-        when(a.getLayoutId()).thenReturn(234);
-        when(a.getSequenceNumber()).thenReturn(1);
-        RemoteViews b = mock(RemoteViews.class);
-        when(b.getLayoutId()).thenReturn(234);
-        when(b.getSequenceNumber()).thenReturn(2);
-
-        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
-        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
-        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
-        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
-        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
-        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
-        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-    }
-
-    @Test
-    public void testRemoteViews_sequenceSame() {
-        RemoteViews a = mock(RemoteViews.class);
-        when(a.getLayoutId()).thenReturn(234);
-        when(a.getSequenceNumber()).thenReturn(1);
-        RemoteViews b = mock(RemoteViews.class);
-        when(b.getLayoutId()).thenReturn(234);
-        when(b.getSequenceNumber()).thenReturn(1);
-
-        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
-        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
-        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
-        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
-        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
-        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
-        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-    }
-
-    @Test
-    public void testActionsDifferent_null() {
-        Notification n1 = new Notification.Builder(mContext, "test")
-                .build();
-        Notification n2 = new Notification.Builder(mContext, "test")
-                .build();
-
-        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testActionsDifferentSame() {
-        PendingIntent intent = mock(PendingIntent.class);
-        Icon icon = mock(Icon.class);
-
-        Notification n1 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
-                .build();
-        Notification n2 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
-                .build();
-
-        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testActionsDifferentText() {
-        PendingIntent intent = mock(PendingIntent.class);
-        Icon icon = mock(Icon.class);
-
-        Notification n1 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
-                .build();
-        Notification n2 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build())
-                .build();
-
-        assertTrue(Notification.areActionsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testActionsDifferentSpannables() {
-        PendingIntent intent = mock(PendingIntent.class);
-        Icon icon = mock(Icon.class);
-
-        Notification n1 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon,
-                        new SpannableStringBuilder().append("test1",
-                                new StyleSpan(Typeface.BOLD),
-                                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE),
-                        intent).build())
-                .build();
-        Notification n2 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "test1", intent).build())
-                .build();
-
-        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testActionsDifferentNumber() {
-        PendingIntent intent = mock(PendingIntent.class);
-        Icon icon = mock(Icon.class);
-
-        Notification n1 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
-                .build();
-        Notification n2 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
-                .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build())
-                .build();
-
-        assertTrue(Notification.areActionsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testActionsDifferentIntent() {
-        PendingIntent intent1 = mock(PendingIntent.class);
-        PendingIntent intent2 = mock(PendingIntent.class);
-        Icon icon = mock(Icon.class);
-
-        Notification n1 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent1).build())
-                .build();
-        Notification n2 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent2).build())
-                .build();
-
-        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testActionsIgnoresRemoteInputs() {
-        PendingIntent intent = mock(PendingIntent.class);
-        Icon icon = mock(Icon.class);
-
-        Notification n1 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
-                        .addRemoteInput(new RemoteInput.Builder("a")
-                                .setChoices(new CharSequence[] {"i", "m"})
-                                .build())
-                        .build())
-                .build();
-        Notification n2 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
-                        .addRemoteInput(new RemoteInput.Builder("a")
-                                .setChoices(new CharSequence[] {"t", "m"})
-                                .build())
-                        .build())
-                .build();
-
-        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testFreeformRemoteInputActionPair_noRemoteInput() {
-        PendingIntent intent = mock(PendingIntent.class);
-        Icon icon = mock(Icon.class);
-        Notification notification = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
-                        .build())
-                .build();
-        assertNull(notification.findRemoteInputActionPair(false));
-    }
-
-    @Test
-    public void testFreeformRemoteInputActionPair_hasRemoteInput() {
-        PendingIntent intent = mock(PendingIntent.class);
-        Icon icon = mock(Icon.class);
-
-        RemoteInput remoteInput = new RemoteInput.Builder("a").build();
-
-        Notification.Action actionWithRemoteInput =
-                new Notification.Action.Builder(icon, "TEXT 1", intent)
-                        .addRemoteInput(remoteInput)
-                        .addRemoteInput(remoteInput)
-                        .build();
-
-        Notification.Action actionWithoutRemoteInput =
-                new Notification.Action.Builder(icon, "TEXT 2", intent)
-                        .build();
-
-        Notification notification = new Notification.Builder(mContext, "test")
-                .addAction(actionWithoutRemoteInput)
-                .addAction(actionWithRemoteInput)
-                .build();
-
-        Pair<RemoteInput, Notification.Action> remoteInputActionPair =
-                notification.findRemoteInputActionPair(false);
-
-        assertNotNull(remoteInputActionPair);
-        assertEquals(remoteInput, remoteInputActionPair.first);
-        assertEquals(actionWithRemoteInput, remoteInputActionPair.second);
-    }
-
-    @Test
-    public void testFreeformRemoteInputActionPair_requestFreeform_noFreeformRemoteInput() {
-        PendingIntent intent = mock(PendingIntent.class);
-        Icon icon = mock(Icon.class);
-        Notification notification = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
-                        .addRemoteInput(
-                                new RemoteInput.Builder("a")
-                                        .setAllowFreeFormInput(false).build())
-                        .build())
-                .build();
-        assertNull(notification.findRemoteInputActionPair(true));
-    }
-
-    @Test
-    public void testFreeformRemoteInputActionPair_requestFreeform_hasFreeformRemoteInput() {
-        PendingIntent intent = mock(PendingIntent.class);
-        Icon icon = mock(Icon.class);
-
-        RemoteInput remoteInput =
-                new RemoteInput.Builder("a").setAllowFreeFormInput(false).build();
-        RemoteInput freeformRemoteInput =
-                new RemoteInput.Builder("b").setAllowFreeFormInput(true).build();
-
-        Notification.Action actionWithFreeformRemoteInput =
-                new Notification.Action.Builder(icon, "TEXT 1", intent)
-                        .addRemoteInput(remoteInput)
-                        .addRemoteInput(freeformRemoteInput)
-                        .build();
-
-        Notification.Action actionWithoutFreeformRemoteInput =
-                new Notification.Action.Builder(icon, "TEXT 2", intent)
-                        .addRemoteInput(remoteInput)
-                        .build();
-
-        Notification notification = new Notification.Builder(mContext, "test")
-                .addAction(actionWithoutFreeformRemoteInput)
-                .addAction(actionWithFreeformRemoteInput)
-                .build();
-
-        Pair<RemoteInput, Notification.Action> remoteInputActionPair =
-                notification.findRemoteInputActionPair(true);
-
-        assertNotNull(remoteInputActionPair);
-        assertEquals(freeformRemoteInput, remoteInputActionPair.first);
-        assertEquals(actionWithFreeformRemoteInput, remoteInputActionPair.second);
-    }
-}
-
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index d5e336b..eed32d7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -40,14 +40,18 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.timeout;
 
+import android.app.ActivityOptions;
 import android.app.WaitResult;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.os.Binder;
 import android.os.ConditionVariable;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.view.Display;
@@ -308,4 +312,40 @@
         waitHandlerIdle(mAtm.mH);
         verify(mRootWindowContainer, timeout(TIMEOUT_MS)).startHomeOnEmptyDisplays("userUnlocked");
     }
+
+    /** Verifies that launch from recents sets the launch cookie on the activity. */
+    @Test
+    public void testStartActivityFromRecents_withLaunchCookie() {
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+
+        IBinder launchCookie = new Binder("test_launch_cookie");
+        ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchCookie(launchCookie);
+        SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(options.toBundle());
+
+        doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(),
+                anyInt(), any());
+
+        mSupervisor.startActivityFromRecents(-1, -1, activity.getRootTaskId(), safeOptions);
+
+        assertThat(activity.mLaunchCookie).isEqualTo(launchCookie);
+        verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions));
+    }
+
+    /** Verifies that launch from recents doesn't set the launch cookie on the activity. */
+    @Test
+    public void testStartActivityFromRecents_withoutLaunchCookie() {
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+
+        SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(
+                ActivityOptions.makeBasic().toBundle());
+
+        doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(),
+                anyInt(), any());
+
+        mSupervisor.startActivityFromRecents(-1, -1, activity.getRootTaskId(), safeOptions);
+
+        assertThat(activity.mLaunchCookie).isNull();
+        verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions));
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 1cd0b19..e30e5db 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -242,7 +242,7 @@
     private IOnBackInvokedCallback createOnBackInvokedCallback() {
         return new IOnBackInvokedCallback.Stub() {
             @Override
-            public void onBackStarted() {
+            public void onBackStarted(BackEvent backEvent) {
             }
 
             @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
index e57ad5d..24e932f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
@@ -57,10 +57,20 @@
                 .setLaunchTaskDisplayArea(token)
                 .setLaunchDisplayId(launchDisplayId)
                 .setCallerDisplayId(callerDisplayId))
-                .selectiveCloneDisplayOptions();
+                .selectiveCloneLaunchOptions();
 
         assertSame(clone.getOriginalOptions().getLaunchTaskDisplayArea(), token);
         assertEquals(clone.getOriginalOptions().getLaunchDisplayId(), launchDisplayId);
         assertEquals(clone.getOriginalOptions().getCallerDisplayId(), callerDisplayId);
     }
+
+    @Test
+    public void test_selectiveCloneLunchRootTask() {
+        final WindowContainerToken token = mock(WindowContainerToken.class);
+        final SafeActivityOptions clone = new SafeActivityOptions(ActivityOptions.makeBasic()
+                .setLaunchRootTask(token))
+                .selectiveCloneLaunchOptions();
+
+        assertSame(clone.getOriginalOptions().getLaunchRootTask(), token);
+    }
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 3da4711..352d8d1 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -136,9 +136,6 @@
     private final RemoteCallbackList<IVoiceInteractionSessionListener>
             mVoiceInteractionSessionListeners = new RemoteCallbackList<>();
 
-    // TODO(b/226201975): remove once RoleService supports pre-created users
-    private final ArrayList<UserHandle> mIgnoredPreCreatedUsers = new ArrayList<>();
-
     public VoiceInteractionManagerService(Context context) {
         super(context);
         mContext = context;
@@ -308,24 +305,14 @@
             return hotwordDetectionConnection.mIdentity;
         }
 
+        // TODO(b/226201975): remove this method once RoleService supports pre-created users
         @Override
         public void onPreCreatedUserConversion(int userId) {
-            Slogf.d(TAG, "onPreCreatedUserConversion(%d)", userId);
-
-            for (int i = 0; i < mIgnoredPreCreatedUsers.size(); i++) {
-                UserHandle preCreatedUser = mIgnoredPreCreatedUsers.get(i);
-                if (preCreatedUser.getIdentifier() == userId) {
-                    Slogf.d(TAG, "Updating role on pre-created user %d", userId);
-                    mServiceStub.mRoleObserver.onRoleHoldersChanged(RoleManager.ROLE_ASSISTANT,
-                            preCreatedUser);
-                    mIgnoredPreCreatedUsers.remove(i);
-                    return;
-                }
-            }
-            Slogf.w(TAG, "onPreCreatedUserConversion(%d): not available on "
-                    + "mIgnoredPreCreatedUserIds (%s)", userId, mIgnoredPreCreatedUsers);
+            Slogf.d(TAG, "onPreCreatedUserConversion(%d): calling onRoleHoldersChanged() again",
+                    userId);
+            mServiceStub.mRoleObserver.onRoleHoldersChanged(RoleManager.ROLE_ASSISTANT,
+                                                UserHandle.of(userId));
         }
-
     }
 
     // implementation entry point and binder service
@@ -807,8 +794,10 @@
             if (TextUtils.isEmpty(curInteractor)) {
                 return null;
             }
-            if (DEBUG) Slog.d(TAG, "getCurInteractor curInteractor=" + curInteractor
+            if (DEBUG) {
+                Slog.d(TAG, "getCurInteractor curInteractor=" + curInteractor
                     + " user=" + userHandle);
+            }
             return ComponentName.unflattenFromString(curInteractor);
         }
 
@@ -816,8 +805,9 @@
             Settings.Secure.putStringForUser(mContext.getContentResolver(),
                     Settings.Secure.VOICE_INTERACTION_SERVICE,
                     comp != null ? comp.flattenToShortString() : "", userHandle);
-            if (DEBUG) Slog.d(TAG, "setCurInteractor comp=" + comp
-                    + " user=" + userHandle);
+            if (DEBUG) {
+                Slog.d(TAG, "setCurInteractor comp=" + comp + " user=" + userHandle);
+            }
         }
 
         ComponentName findAvailRecognizer(String prefPackage, int userHandle) {
@@ -1912,7 +1902,6 @@
                 pw.println("  mTemporarilyDisabled: " + mTemporarilyDisabled);
                 pw.println("  mCurUser: " + mCurUser);
                 pw.println("  mCurUserSupported: " + mCurUserSupported);
-                pw.println("  mIgnoredPreCreatedUsers: " + mIgnoredPreCreatedUsers);
                 dumpSupportedUsers(pw, "  ");
                 mDbHelper.dump(pw);
                 if (mImpl == null) {
@@ -2026,6 +2015,11 @@
 
                 List<String> roleHolders = mRm.getRoleHoldersAsUser(roleName, user);
 
+                if (DEBUG) {
+                    Slogf.d(TAG, "onRoleHoldersChanged(%s, %s): roleHolders=%s", roleName, user,
+                            roleHolders);
+                }
+
                 // TODO(b/226201975): this method is beling called when a pre-created user is added,
                 // at which point it doesn't have any role holders. But it's not called again when
                 // the actual user is added (i.e., when the  pre-created user is converted), so we
@@ -2036,9 +2030,9 @@
                 if (roleHolders.isEmpty()) {
                     UserInfo userInfo = mUserManagerInternal.getUserInfo(user.getIdentifier());
                     if (userInfo != null && userInfo.preCreated) {
-                        Slogf.d(TAG, "onRoleHoldersChanged(): ignoring pre-created user %s for now",
-                                userInfo.toFullString());
-                        mIgnoredPreCreatedUsers.add(user);
+                        Slogf.d(TAG, "onRoleHoldersChanged(): ignoring pre-created user %s for now,"
+                                + " this method will be called again when it's converted to a real"
+                                + " user", userInfo.toFullString());
                         return;
                     }
                 }
diff --git a/tools/xmlpersistence/src/main/kotlin/Generator.kt b/tools/xmlpersistence/src/main/kotlin/Generator.kt
index b2c5f4a..8e62388 100644
--- a/tools/xmlpersistence/src/main/kotlin/Generator.kt
+++ b/tools/xmlpersistence/src/main/kotlin/Generator.kt
@@ -149,6 +149,7 @@
                 when (field) {
                     is ClassFieldInfo -> this += field.allClassFields
                     is ListFieldInfo -> this += field.element.allClassFields
+                    else -> {}
                 }
             }
         }